| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | #include <kunit/test.h> |
| 3 | #include <linux/fcntl.h> |
| 4 | #include <linux/file.h> |
| 5 | #include <linux/fs.h> |
| 6 | #include <linux/init_syscalls.h> |
| 7 | #include <linux/stringify.h> |
| 8 | #include <linux/timekeeping.h> |
| 9 | #include "initramfs_internal.h" |
| 10 | |
| 11 | struct initramfs_test_cpio { |
| 12 | char *magic; |
| 13 | unsigned int ino; |
| 14 | unsigned int mode; |
| 15 | unsigned int uid; |
| 16 | unsigned int gid; |
| 17 | unsigned int nlink; |
| 18 | unsigned int mtime; |
| 19 | unsigned int filesize; |
| 20 | unsigned int devmajor; |
| 21 | unsigned int devminor; |
| 22 | unsigned int rdevmajor; |
| 23 | unsigned int rdevminor; |
| 24 | unsigned int namesize; |
| 25 | unsigned int csum; |
| 26 | char *fname; |
| 27 | char *data; |
| 28 | }; |
| 29 | |
| 30 | static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out) |
| 31 | { |
| 32 | int i; |
| 33 | size_t off = 0; |
| 34 | |
| 35 | for (i = 0; i < csz; i++) { |
| 36 | char *pos = &out[off]; |
| 37 | struct initramfs_test_cpio *c = &cs[i]; |
| 38 | size_t thislen; |
| 39 | |
| 40 | /* +1 to account for nulterm */ |
| 41 | thislen = sprintf(buf: pos, fmt: "%s" |
| 42 | "%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x" |
| 43 | "%s" , |
| 44 | c->magic, c->ino, c->mode, c->uid, c->gid, c->nlink, |
| 45 | c->mtime, c->filesize, c->devmajor, c->devminor, |
| 46 | c->rdevmajor, c->rdevminor, c->namesize, c->csum, |
| 47 | c->fname) + 1; |
| 48 | pr_debug("packing (%zu): %.*s\n" , thislen, (int)thislen, pos); |
| 49 | off += thislen; |
| 50 | while (off & 3) |
| 51 | out[off++] = '\0'; |
| 52 | |
| 53 | memcpy(&out[off], c->data, c->filesize); |
| 54 | off += c->filesize; |
| 55 | while (off & 3) |
| 56 | out[off++] = '\0'; |
| 57 | } |
| 58 | |
| 59 | return off; |
| 60 | } |
| 61 | |
| 62 | static void __init (struct kunit *test) |
| 63 | { |
| 64 | char *err, *cpio_srcbuf; |
| 65 | size_t len; |
| 66 | struct timespec64 ts_before, ts_after; |
| 67 | struct kstat st = {}; |
| 68 | struct initramfs_test_cpio c[] = { { |
| 69 | .magic = "070701" , |
| 70 | .ino = 1, |
| 71 | .mode = S_IFREG | 0777, |
| 72 | .uid = 12, |
| 73 | .gid = 34, |
| 74 | .nlink = 1, |
| 75 | .mtime = 56, |
| 76 | .filesize = 0, |
| 77 | .devmajor = 0, |
| 78 | .devminor = 1, |
| 79 | .rdevmajor = 0, |
| 80 | .rdevminor = 0, |
| 81 | .namesize = sizeof("initramfs_test_extract" ), |
| 82 | .csum = 0, |
| 83 | .fname = "initramfs_test_extract" , |
| 84 | }, { |
| 85 | .magic = "070701" , |
| 86 | .ino = 2, |
| 87 | .mode = S_IFDIR | 0777, |
| 88 | .nlink = 1, |
| 89 | .mtime = 57, |
| 90 | .devminor = 1, |
| 91 | .namesize = sizeof("initramfs_test_extract_dir" ), |
| 92 | .fname = "initramfs_test_extract_dir" , |
| 93 | }, { |
| 94 | .magic = "070701" , |
| 95 | .namesize = sizeof("TRAILER!!!" ), |
| 96 | .fname = "TRAILER!!!" , |
| 97 | } }; |
| 98 | |
| 99 | /* +3 to cater for any 4-byte end-alignment */ |
| 100 | cpio_srcbuf = kzalloc(ARRAY_SIZE(c) * (CPIO_HDRLEN + PATH_MAX + 3), |
| 101 | GFP_KERNEL); |
| 102 | len = fill_cpio(cs: c, ARRAY_SIZE(c), out: cpio_srcbuf); |
| 103 | |
| 104 | ktime_get_real_ts64(tv: &ts_before); |
| 105 | err = unpack_to_rootfs(buf: cpio_srcbuf, len); |
| 106 | ktime_get_real_ts64(tv: &ts_after); |
| 107 | if (err) { |
| 108 | KUNIT_FAIL(test, "unpack failed %s" , err); |
| 109 | goto out; |
| 110 | } |
| 111 | |
| 112 | KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st, 0), 0); |
| 113 | KUNIT_EXPECT_TRUE(test, S_ISREG(st.mode)); |
| 114 | KUNIT_EXPECT_TRUE(test, uid_eq(st.uid, KUIDT_INIT(c[0].uid))); |
| 115 | KUNIT_EXPECT_TRUE(test, gid_eq(st.gid, KGIDT_INIT(c[0].gid))); |
| 116 | KUNIT_EXPECT_EQ(test, st.nlink, 1); |
| 117 | if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) { |
| 118 | KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[0].mtime); |
| 119 | } else { |
| 120 | KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec); |
| 121 | KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec); |
| 122 | } |
| 123 | KUNIT_EXPECT_EQ(test, st.blocks, c[0].filesize); |
| 124 | |
| 125 | KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st, 0), 0); |
| 126 | KUNIT_EXPECT_TRUE(test, S_ISDIR(st.mode)); |
| 127 | if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) { |
| 128 | KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[1].mtime); |
| 129 | } else { |
| 130 | KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec); |
| 131 | KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec); |
| 132 | } |
| 133 | |
| 134 | KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); |
| 135 | KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0); |
| 136 | out: |
| 137 | kfree(objp: cpio_srcbuf); |
| 138 | } |
| 139 | |
| 140 | /* |
| 141 | * Don't terminate filename. Previously, the cpio filename field was passed |
| 142 | * directly to filp_open(collected, O_CREAT|..) without nulterm checks. See |
| 143 | * https://lore.kernel.org/linux-fsdevel/20241030035509.20194-2-ddiss@suse.de |
| 144 | */ |
| 145 | static void __init initramfs_test_fname_overrun(struct kunit *test) |
| 146 | { |
| 147 | char *err, *cpio_srcbuf; |
| 148 | size_t len, suffix_off; |
| 149 | struct initramfs_test_cpio c[] = { { |
| 150 | .magic = "070701" , |
| 151 | .ino = 1, |
| 152 | .mode = S_IFREG | 0777, |
| 153 | .uid = 0, |
| 154 | .gid = 0, |
| 155 | .nlink = 1, |
| 156 | .mtime = 1, |
| 157 | .filesize = 0, |
| 158 | .devmajor = 0, |
| 159 | .devminor = 1, |
| 160 | .rdevmajor = 0, |
| 161 | .rdevminor = 0, |
| 162 | .namesize = sizeof("initramfs_test_fname_overrun" ), |
| 163 | .csum = 0, |
| 164 | .fname = "initramfs_test_fname_overrun" , |
| 165 | } }; |
| 166 | |
| 167 | /* |
| 168 | * poison cpio source buffer, so we can detect overrun. source |
| 169 | * buffer is used by read_into() when hdr or fname |
| 170 | * are already available (e.g. no compression). |
| 171 | */ |
| 172 | cpio_srcbuf = kmalloc(CPIO_HDRLEN + PATH_MAX + 3, GFP_KERNEL); |
| 173 | memset(cpio_srcbuf, 'B', CPIO_HDRLEN + PATH_MAX + 3); |
| 174 | /* limit overrun to avoid crashes / filp_open() ENAMETOOLONG */ |
| 175 | cpio_srcbuf[CPIO_HDRLEN + strlen(c[0].fname) + 20] = '\0'; |
| 176 | |
| 177 | len = fill_cpio(cs: c, ARRAY_SIZE(c), out: cpio_srcbuf); |
| 178 | /* overwrite trailing fname terminator and padding */ |
| 179 | suffix_off = len - 1; |
| 180 | while (cpio_srcbuf[suffix_off] == '\0') { |
| 181 | cpio_srcbuf[suffix_off] = 'P'; |
| 182 | suffix_off--; |
| 183 | } |
| 184 | |
| 185 | err = unpack_to_rootfs(buf: cpio_srcbuf, len); |
| 186 | KUNIT_EXPECT_NOT_NULL(test, err); |
| 187 | |
| 188 | kfree(objp: cpio_srcbuf); |
| 189 | } |
| 190 | |
| 191 | static void __init initramfs_test_data(struct kunit *test) |
| 192 | { |
| 193 | char *err, *cpio_srcbuf; |
| 194 | size_t len; |
| 195 | struct file *file; |
| 196 | struct initramfs_test_cpio c[] = { { |
| 197 | .magic = "070701" , |
| 198 | .ino = 1, |
| 199 | .mode = S_IFREG | 0777, |
| 200 | .uid = 0, |
| 201 | .gid = 0, |
| 202 | .nlink = 1, |
| 203 | .mtime = 1, |
| 204 | .filesize = sizeof("ASDF" ) - 1, |
| 205 | .devmajor = 0, |
| 206 | .devminor = 1, |
| 207 | .rdevmajor = 0, |
| 208 | .rdevminor = 0, |
| 209 | .namesize = sizeof("initramfs_test_data" ), |
| 210 | .csum = 0, |
| 211 | .fname = "initramfs_test_data" , |
| 212 | .data = "ASDF" , |
| 213 | } }; |
| 214 | |
| 215 | /* +6 for max name and data 4-byte padding */ |
| 216 | cpio_srcbuf = kmalloc(CPIO_HDRLEN + c[0].namesize + c[0].filesize + 6, |
| 217 | GFP_KERNEL); |
| 218 | |
| 219 | len = fill_cpio(cs: c, ARRAY_SIZE(c), out: cpio_srcbuf); |
| 220 | |
| 221 | err = unpack_to_rootfs(buf: cpio_srcbuf, len); |
| 222 | KUNIT_EXPECT_NULL(test, err); |
| 223 | |
| 224 | file = filp_open(c[0].fname, O_RDONLY, 0); |
| 225 | if (IS_ERR(ptr: file)) { |
| 226 | KUNIT_FAIL(test, "open failed" ); |
| 227 | goto out; |
| 228 | } |
| 229 | |
| 230 | /* read back file contents into @cpio_srcbuf and confirm match */ |
| 231 | len = kernel_read(file, cpio_srcbuf, c[0].filesize, NULL); |
| 232 | KUNIT_EXPECT_EQ(test, len, c[0].filesize); |
| 233 | KUNIT_EXPECT_MEMEQ(test, cpio_srcbuf, c[0].data, len); |
| 234 | |
| 235 | fput(file); |
| 236 | KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); |
| 237 | out: |
| 238 | kfree(objp: cpio_srcbuf); |
| 239 | } |
| 240 | |
| 241 | static void __init initramfs_test_csum(struct kunit *test) |
| 242 | { |
| 243 | char *err, *cpio_srcbuf; |
| 244 | size_t len; |
| 245 | struct initramfs_test_cpio c[] = { { |
| 246 | /* 070702 magic indicates a valid csum is present */ |
| 247 | .magic = "070702" , |
| 248 | .ino = 1, |
| 249 | .mode = S_IFREG | 0777, |
| 250 | .nlink = 1, |
| 251 | .filesize = sizeof("ASDF" ) - 1, |
| 252 | .devminor = 1, |
| 253 | .namesize = sizeof("initramfs_test_csum" ), |
| 254 | .csum = 'A' + 'S' + 'D' + 'F', |
| 255 | .fname = "initramfs_test_csum" , |
| 256 | .data = "ASDF" , |
| 257 | }, { |
| 258 | /* mix csum entry above with no-csum entry below */ |
| 259 | .magic = "070701" , |
| 260 | .ino = 2, |
| 261 | .mode = S_IFREG | 0777, |
| 262 | .nlink = 1, |
| 263 | .filesize = sizeof("ASDF" ) - 1, |
| 264 | .devminor = 1, |
| 265 | .namesize = sizeof("initramfs_test_csum_not_here" ), |
| 266 | /* csum ignored */ |
| 267 | .csum = 5555, |
| 268 | .fname = "initramfs_test_csum_not_here" , |
| 269 | .data = "ASDF" , |
| 270 | } }; |
| 271 | |
| 272 | cpio_srcbuf = kmalloc(8192, GFP_KERNEL); |
| 273 | |
| 274 | len = fill_cpio(cs: c, ARRAY_SIZE(c), out: cpio_srcbuf); |
| 275 | |
| 276 | err = unpack_to_rootfs(buf: cpio_srcbuf, len); |
| 277 | KUNIT_EXPECT_NULL(test, err); |
| 278 | |
| 279 | KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); |
| 280 | KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0); |
| 281 | |
| 282 | /* mess up the csum and confirm that unpack fails */ |
| 283 | c[0].csum--; |
| 284 | len = fill_cpio(cs: c, ARRAY_SIZE(c), out: cpio_srcbuf); |
| 285 | |
| 286 | err = unpack_to_rootfs(buf: cpio_srcbuf, len); |
| 287 | KUNIT_EXPECT_NOT_NULL(test, err); |
| 288 | |
| 289 | /* |
| 290 | * file (with content) is still retained in case of bad-csum abort. |
| 291 | * Perhaps we should change this. |
| 292 | */ |
| 293 | KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); |
| 294 | KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), -ENOENT); |
| 295 | kfree(objp: cpio_srcbuf); |
| 296 | } |
| 297 | |
| 298 | /* |
| 299 | * hardlink hashtable may leak when the archive omits a trailer: |
| 300 | * https://lore.kernel.org/r/20241107002044.16477-10-ddiss@suse.de/ |
| 301 | */ |
| 302 | static void __init initramfs_test_hardlink(struct kunit *test) |
| 303 | { |
| 304 | char *err, *cpio_srcbuf; |
| 305 | size_t len; |
| 306 | struct kstat st0, st1; |
| 307 | struct initramfs_test_cpio c[] = { { |
| 308 | .magic = "070701" , |
| 309 | .ino = 1, |
| 310 | .mode = S_IFREG | 0777, |
| 311 | .nlink = 2, |
| 312 | .devminor = 1, |
| 313 | .namesize = sizeof("initramfs_test_hardlink" ), |
| 314 | .fname = "initramfs_test_hardlink" , |
| 315 | }, { |
| 316 | /* hardlink data is present in last archive entry */ |
| 317 | .magic = "070701" , |
| 318 | .ino = 1, |
| 319 | .mode = S_IFREG | 0777, |
| 320 | .nlink = 2, |
| 321 | .filesize = sizeof("ASDF" ) - 1, |
| 322 | .devminor = 1, |
| 323 | .namesize = sizeof("initramfs_test_hardlink_link" ), |
| 324 | .fname = "initramfs_test_hardlink_link" , |
| 325 | .data = "ASDF" , |
| 326 | } }; |
| 327 | |
| 328 | cpio_srcbuf = kmalloc(8192, GFP_KERNEL); |
| 329 | |
| 330 | len = fill_cpio(cs: c, ARRAY_SIZE(c), out: cpio_srcbuf); |
| 331 | |
| 332 | err = unpack_to_rootfs(buf: cpio_srcbuf, len); |
| 333 | KUNIT_EXPECT_NULL(test, err); |
| 334 | |
| 335 | KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0); |
| 336 | KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0); |
| 337 | KUNIT_EXPECT_EQ(test, st0.ino, st1.ino); |
| 338 | KUNIT_EXPECT_EQ(test, st0.nlink, 2); |
| 339 | KUNIT_EXPECT_EQ(test, st1.nlink, 2); |
| 340 | |
| 341 | KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); |
| 342 | KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0); |
| 343 | |
| 344 | kfree(objp: cpio_srcbuf); |
| 345 | } |
| 346 | |
| 347 | #define INITRAMFS_TEST_MANY_LIMIT 1000 |
| 348 | #define INITRAMFS_TEST_MANY_PATH_MAX (sizeof("initramfs_test_many-") \ |
| 349 | + sizeof(__stringify(INITRAMFS_TEST_MANY_LIMIT))) |
| 350 | static void __init initramfs_test_many(struct kunit *test) |
| 351 | { |
| 352 | char *err, *cpio_srcbuf, *p; |
| 353 | size_t len = INITRAMFS_TEST_MANY_LIMIT * |
| 354 | (CPIO_HDRLEN + INITRAMFS_TEST_MANY_PATH_MAX + 3); |
| 355 | char thispath[INITRAMFS_TEST_MANY_PATH_MAX]; |
| 356 | int i; |
| 357 | |
| 358 | p = cpio_srcbuf = kmalloc(len, GFP_KERNEL); |
| 359 | |
| 360 | for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) { |
| 361 | struct initramfs_test_cpio c = { |
| 362 | .magic = "070701" , |
| 363 | .ino = i, |
| 364 | .mode = S_IFREG | 0777, |
| 365 | .nlink = 1, |
| 366 | .devminor = 1, |
| 367 | .fname = thispath, |
| 368 | }; |
| 369 | |
| 370 | c.namesize = 1 + sprintf(buf: thispath, fmt: "initramfs_test_many-%d" , i); |
| 371 | p += fill_cpio(cs: &c, csz: 1, out: p); |
| 372 | } |
| 373 | |
| 374 | len = p - cpio_srcbuf; |
| 375 | err = unpack_to_rootfs(buf: cpio_srcbuf, len); |
| 376 | KUNIT_EXPECT_NULL(test, err); |
| 377 | |
| 378 | for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) { |
| 379 | sprintf(buf: thispath, fmt: "initramfs_test_many-%d" , i); |
| 380 | KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0); |
| 381 | } |
| 382 | |
| 383 | kfree(objp: cpio_srcbuf); |
| 384 | } |
| 385 | |
| 386 | /* |
| 387 | * The kunit_case/_suite struct cannot be marked as __initdata as this will be |
| 388 | * used in debugfs to retrieve results after test has run. |
| 389 | */ |
| 390 | static struct kunit_case __refdata initramfs_test_cases[] = { |
| 391 | KUNIT_CASE(initramfs_test_extract), |
| 392 | KUNIT_CASE(initramfs_test_fname_overrun), |
| 393 | KUNIT_CASE(initramfs_test_data), |
| 394 | KUNIT_CASE(initramfs_test_csum), |
| 395 | KUNIT_CASE(initramfs_test_hardlink), |
| 396 | KUNIT_CASE(initramfs_test_many), |
| 397 | {}, |
| 398 | }; |
| 399 | |
| 400 | static struct kunit_suite initramfs_test_suite = { |
| 401 | .name = "initramfs" , |
| 402 | .test_cases = initramfs_test_cases, |
| 403 | }; |
| 404 | kunit_test_init_section_suites(&initramfs_test_suite); |
| 405 | |
| 406 | MODULE_DESCRIPTION("Initramfs KUnit test suite" ); |
| 407 | MODULE_LICENSE("GPL v2" ); |
| 408 | |