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(pos, "%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 49 pr_debug("packing (%zu): %.*s\n", thislen, (int)thislen, pos); 50 if (thislen != CPIO_HDRLEN + c->namesize) 51 pr_debug("padded to: %u\n", CPIO_HDRLEN + c->namesize); 52 off += CPIO_HDRLEN + c->namesize; 53 while (off & 3) 54 out[off++] = '\0'; 55 56 memcpy(&out[off], c->data, c->filesize); 57 off += c->filesize; 58 while (off & 3) 59 out[off++] = '\0'; 60 } 61 62 return off; 63 } 64 65 static void __init initramfs_test_extract(struct kunit *test) 66 { 67 char *err, *cpio_srcbuf; 68 size_t len; 69 struct timespec64 ts_before, ts_after; 70 struct kstat st = {}; 71 struct initramfs_test_cpio c[] = { { 72 .magic = "070701", 73 .ino = 1, 74 .mode = S_IFREG | 0777, 75 .uid = 12, 76 .gid = 34, 77 .nlink = 1, 78 .mtime = 56, 79 .filesize = 0, 80 .devmajor = 0, 81 .devminor = 1, 82 .rdevmajor = 0, 83 .rdevminor = 0, 84 .namesize = sizeof("initramfs_test_extract"), 85 .csum = 0, 86 .fname = "initramfs_test_extract", 87 }, { 88 .magic = "070701", 89 .ino = 2, 90 .mode = S_IFDIR | 0777, 91 .nlink = 1, 92 .mtime = 57, 93 .devminor = 1, 94 .namesize = sizeof("initramfs_test_extract_dir"), 95 .fname = "initramfs_test_extract_dir", 96 }, { 97 .magic = "070701", 98 .namesize = sizeof("TRAILER!!!"), 99 .fname = "TRAILER!!!", 100 } }; 101 102 /* +3 to cater for any 4-byte end-alignment */ 103 cpio_srcbuf = kzalloc(ARRAY_SIZE(c) * (CPIO_HDRLEN + PATH_MAX + 3), 104 GFP_KERNEL); 105 len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); 106 107 ktime_get_real_ts64(&ts_before); 108 err = unpack_to_rootfs(cpio_srcbuf, len); 109 ktime_get_real_ts64(&ts_after); 110 if (err) { 111 KUNIT_FAIL(test, "unpack failed %s", err); 112 goto out; 113 } 114 115 KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st, 0), 0); 116 KUNIT_EXPECT_TRUE(test, S_ISREG(st.mode)); 117 KUNIT_EXPECT_TRUE(test, uid_eq(st.uid, KUIDT_INIT(c[0].uid))); 118 KUNIT_EXPECT_TRUE(test, gid_eq(st.gid, KGIDT_INIT(c[0].gid))); 119 KUNIT_EXPECT_EQ(test, st.nlink, 1); 120 if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) { 121 KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[0].mtime); 122 } else { 123 KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec); 124 KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec); 125 } 126 KUNIT_EXPECT_EQ(test, st.blocks, c[0].filesize); 127 128 KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st, 0), 0); 129 KUNIT_EXPECT_TRUE(test, S_ISDIR(st.mode)); 130 if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) { 131 KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[1].mtime); 132 } else { 133 KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec); 134 KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec); 135 } 136 137 KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); 138 KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0); 139 out: 140 kfree(cpio_srcbuf); 141 } 142 143 /* 144 * Don't terminate filename. Previously, the cpio filename field was passed 145 * directly to filp_open(collected, O_CREAT|..) without nulterm checks. See 146 * https://lore.kernel.org/linux-fsdevel/20241030035509.20194-2-ddiss@suse.de 147 */ 148 static void __init initramfs_test_fname_overrun(struct kunit *test) 149 { 150 char *err, *cpio_srcbuf; 151 size_t len, suffix_off; 152 struct initramfs_test_cpio c[] = { { 153 .magic = "070701", 154 .ino = 1, 155 .mode = S_IFREG | 0777, 156 .uid = 0, 157 .gid = 0, 158 .nlink = 1, 159 .mtime = 1, 160 .filesize = 0, 161 .devmajor = 0, 162 .devminor = 1, 163 .rdevmajor = 0, 164 .rdevminor = 0, 165 .namesize = sizeof("initramfs_test_fname_overrun"), 166 .csum = 0, 167 .fname = "initramfs_test_fname_overrun", 168 } }; 169 170 /* 171 * poison cpio source buffer, so we can detect overrun. source 172 * buffer is used by read_into() when hdr or fname 173 * are already available (e.g. no compression). 174 */ 175 cpio_srcbuf = kmalloc(CPIO_HDRLEN + PATH_MAX + 3, GFP_KERNEL); 176 memset(cpio_srcbuf, 'B', CPIO_HDRLEN + PATH_MAX + 3); 177 /* limit overrun to avoid crashes / filp_open() ENAMETOOLONG */ 178 cpio_srcbuf[CPIO_HDRLEN + strlen(c[0].fname) + 20] = '\0'; 179 180 len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); 181 /* overwrite trailing fname terminator and padding */ 182 suffix_off = len - 1; 183 while (cpio_srcbuf[suffix_off] == '\0') { 184 cpio_srcbuf[suffix_off] = 'P'; 185 suffix_off--; 186 } 187 188 err = unpack_to_rootfs(cpio_srcbuf, len); 189 KUNIT_EXPECT_NOT_NULL(test, err); 190 191 kfree(cpio_srcbuf); 192 } 193 194 static void __init initramfs_test_data(struct kunit *test) 195 { 196 char *err, *cpio_srcbuf; 197 size_t len; 198 struct file *file; 199 struct initramfs_test_cpio c[] = { { 200 .magic = "070701", 201 .ino = 1, 202 .mode = S_IFREG | 0777, 203 .uid = 0, 204 .gid = 0, 205 .nlink = 1, 206 .mtime = 1, 207 .filesize = sizeof("ASDF") - 1, 208 .devmajor = 0, 209 .devminor = 1, 210 .rdevmajor = 0, 211 .rdevminor = 0, 212 .namesize = sizeof("initramfs_test_data"), 213 .csum = 0, 214 .fname = "initramfs_test_data", 215 .data = "ASDF", 216 } }; 217 218 /* +6 for max name and data 4-byte padding */ 219 cpio_srcbuf = kmalloc(CPIO_HDRLEN + c[0].namesize + c[0].filesize + 6, 220 GFP_KERNEL); 221 222 len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); 223 224 err = unpack_to_rootfs(cpio_srcbuf, len); 225 KUNIT_EXPECT_NULL(test, err); 226 227 file = filp_open(c[0].fname, O_RDONLY, 0); 228 if (IS_ERR(file)) { 229 KUNIT_FAIL(test, "open failed"); 230 goto out; 231 } 232 233 /* read back file contents into @cpio_srcbuf and confirm match */ 234 len = kernel_read(file, cpio_srcbuf, c[0].filesize, NULL); 235 KUNIT_EXPECT_EQ(test, len, c[0].filesize); 236 KUNIT_EXPECT_MEMEQ(test, cpio_srcbuf, c[0].data, len); 237 238 fput(file); 239 KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); 240 out: 241 kfree(cpio_srcbuf); 242 } 243 244 static void __init initramfs_test_csum(struct kunit *test) 245 { 246 char *err, *cpio_srcbuf; 247 size_t len; 248 struct initramfs_test_cpio c[] = { { 249 /* 070702 magic indicates a valid csum is present */ 250 .magic = "070702", 251 .ino = 1, 252 .mode = S_IFREG | 0777, 253 .nlink = 1, 254 .filesize = sizeof("ASDF") - 1, 255 .devminor = 1, 256 .namesize = sizeof("initramfs_test_csum"), 257 .csum = 'A' + 'S' + 'D' + 'F', 258 .fname = "initramfs_test_csum", 259 .data = "ASDF", 260 }, { 261 /* mix csum entry above with no-csum entry below */ 262 .magic = "070701", 263 .ino = 2, 264 .mode = S_IFREG | 0777, 265 .nlink = 1, 266 .filesize = sizeof("ASDF") - 1, 267 .devminor = 1, 268 .namesize = sizeof("initramfs_test_csum_not_here"), 269 /* csum ignored */ 270 .csum = 5555, 271 .fname = "initramfs_test_csum_not_here", 272 .data = "ASDF", 273 } }; 274 275 cpio_srcbuf = kmalloc(8192, GFP_KERNEL); 276 277 len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); 278 279 err = unpack_to_rootfs(cpio_srcbuf, len); 280 KUNIT_EXPECT_NULL(test, err); 281 282 KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); 283 KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0); 284 285 /* mess up the csum and confirm that unpack fails */ 286 c[0].csum--; 287 len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); 288 289 err = unpack_to_rootfs(cpio_srcbuf, len); 290 KUNIT_EXPECT_NOT_NULL(test, err); 291 292 /* 293 * file (with content) is still retained in case of bad-csum abort. 294 * Perhaps we should change this. 295 */ 296 KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); 297 KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), -ENOENT); 298 kfree(cpio_srcbuf); 299 } 300 301 /* 302 * hardlink hashtable may leak when the archive omits a trailer: 303 * https://lore.kernel.org/r/20241107002044.16477-10-ddiss@suse.de/ 304 */ 305 static void __init initramfs_test_hardlink(struct kunit *test) 306 { 307 char *err, *cpio_srcbuf; 308 size_t len; 309 struct kstat st0, st1; 310 struct initramfs_test_cpio c[] = { { 311 .magic = "070701", 312 .ino = 1, 313 .mode = S_IFREG | 0777, 314 .nlink = 2, 315 .devminor = 1, 316 .namesize = sizeof("initramfs_test_hardlink"), 317 .fname = "initramfs_test_hardlink", 318 }, { 319 /* hardlink data is present in last archive entry */ 320 .magic = "070701", 321 .ino = 1, 322 .mode = S_IFREG | 0777, 323 .nlink = 2, 324 .filesize = sizeof("ASDF") - 1, 325 .devminor = 1, 326 .namesize = sizeof("initramfs_test_hardlink_link"), 327 .fname = "initramfs_test_hardlink_link", 328 .data = "ASDF", 329 } }; 330 331 cpio_srcbuf = kmalloc(8192, GFP_KERNEL); 332 333 len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf); 334 335 err = unpack_to_rootfs(cpio_srcbuf, len); 336 KUNIT_EXPECT_NULL(test, err); 337 338 KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0); 339 KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0); 340 KUNIT_EXPECT_EQ(test, st0.ino, st1.ino); 341 KUNIT_EXPECT_EQ(test, st0.nlink, 2); 342 KUNIT_EXPECT_EQ(test, st1.nlink, 2); 343 344 KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); 345 KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0); 346 347 kfree(cpio_srcbuf); 348 } 349 350 #define INITRAMFS_TEST_MANY_LIMIT 1000 351 #define INITRAMFS_TEST_MANY_PATH_MAX (sizeof("initramfs_test_many-") \ 352 + sizeof(__stringify(INITRAMFS_TEST_MANY_LIMIT))) 353 static void __init initramfs_test_many(struct kunit *test) 354 { 355 char *err, *cpio_srcbuf, *p; 356 size_t len = INITRAMFS_TEST_MANY_LIMIT * 357 (CPIO_HDRLEN + INITRAMFS_TEST_MANY_PATH_MAX + 3); 358 char thispath[INITRAMFS_TEST_MANY_PATH_MAX]; 359 int i; 360 361 p = cpio_srcbuf = kmalloc(len, GFP_KERNEL); 362 363 for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) { 364 struct initramfs_test_cpio c = { 365 .magic = "070701", 366 .ino = i, 367 .mode = S_IFREG | 0777, 368 .nlink = 1, 369 .devminor = 1, 370 .fname = thispath, 371 }; 372 373 c.namesize = 1 + sprintf(thispath, "initramfs_test_many-%d", i); 374 p += fill_cpio(&c, 1, p); 375 } 376 377 len = p - cpio_srcbuf; 378 err = unpack_to_rootfs(cpio_srcbuf, len); 379 KUNIT_EXPECT_NULL(test, err); 380 381 for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) { 382 sprintf(thispath, "initramfs_test_many-%d", i); 383 KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0); 384 } 385 386 kfree(cpio_srcbuf); 387 } 388 389 /* 390 * An initramfs filename is namesize in length, including the zero-terminator. 391 * A filename can be zero-terminated prior to namesize, with the remainder used 392 * as padding. This can be useful for e.g. alignment of file data segments with 393 * a 4KB filesystem block, allowing for extent sharing (reflinks) between cpio 394 * source and destination. This hack works with both GNU cpio and initramfs, as 395 * long as PATH_MAX isn't exceeded. 396 */ 397 static void __init initramfs_test_fname_pad(struct kunit *test) 398 { 399 char *err; 400 size_t len; 401 struct file *file; 402 char fdata[] = "this file data is aligned at 4K in the archive"; 403 struct test_fname_pad { 404 char padded_fname[4096 - CPIO_HDRLEN]; 405 char cpio_srcbuf[CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)]; 406 } *tbufs = kzalloc(sizeof(struct test_fname_pad), GFP_KERNEL); 407 struct initramfs_test_cpio c[] = { { 408 .magic = "070701", 409 .ino = 1, 410 .mode = S_IFREG | 0777, 411 .uid = 0, 412 .gid = 0, 413 .nlink = 1, 414 .mtime = 1, 415 .filesize = sizeof(fdata), 416 .devmajor = 0, 417 .devminor = 1, 418 .rdevmajor = 0, 419 .rdevminor = 0, 420 /* align file data at 4K archive offset via padded fname */ 421 .namesize = 4096 - CPIO_HDRLEN, 422 .csum = 0, 423 .fname = tbufs->padded_fname, 424 .data = fdata, 425 } }; 426 427 memcpy(tbufs->padded_fname, "padded_fname", sizeof("padded_fname")); 428 len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_srcbuf); 429 430 err = unpack_to_rootfs(tbufs->cpio_srcbuf, len); 431 KUNIT_EXPECT_NULL(test, err); 432 433 file = filp_open(c[0].fname, O_RDONLY, 0); 434 if (IS_ERR(file)) { 435 KUNIT_FAIL(test, "open failed"); 436 goto out; 437 } 438 439 /* read back file contents into @cpio_srcbuf and confirm match */ 440 len = kernel_read(file, tbufs->cpio_srcbuf, c[0].filesize, NULL); 441 KUNIT_EXPECT_EQ(test, len, c[0].filesize); 442 KUNIT_EXPECT_MEMEQ(test, tbufs->cpio_srcbuf, c[0].data, len); 443 444 fput(file); 445 KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0); 446 out: 447 kfree(tbufs); 448 } 449 450 /* 451 * The kunit_case/_suite struct cannot be marked as __initdata as this will be 452 * used in debugfs to retrieve results after test has run. 453 */ 454 static struct kunit_case __refdata initramfs_test_cases[] = { 455 KUNIT_CASE(initramfs_test_extract), 456 KUNIT_CASE(initramfs_test_fname_overrun), 457 KUNIT_CASE(initramfs_test_data), 458 KUNIT_CASE(initramfs_test_csum), 459 KUNIT_CASE(initramfs_test_hardlink), 460 KUNIT_CASE(initramfs_test_many), 461 KUNIT_CASE(initramfs_test_fname_pad), 462 {}, 463 }; 464 465 static struct kunit_suite initramfs_test_suite = { 466 .name = "initramfs", 467 .test_cases = initramfs_test_cases, 468 }; 469 kunit_test_init_section_suites(&initramfs_test_suite); 470 471 MODULE_DESCRIPTION("Initramfs KUnit test suite"); 472 MODULE_LICENSE("GPL v2"); 473