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 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 initramfs_test_extract(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(c, ARRAY_SIZE(c), cpio_srcbuf); 103 104 ktime_get_real_ts64(&ts_before); 105 err = unpack_to_rootfs(cpio_srcbuf, len); 106 ktime_get_real_ts64(&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(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(c, ARRAY_SIZE(c), 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(cpio_srcbuf, len); 186 KUNIT_EXPECT_NOT_NULL(test, err); 187 188 kfree(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(c, ARRAY_SIZE(c), cpio_srcbuf); 220 221 err = unpack_to_rootfs(cpio_srcbuf, len); 222 KUNIT_EXPECT_NULL(test, err); 223 224 file = filp_open(c[0].fname, O_RDONLY, 0); 225 if (IS_ERR(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(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(c, ARRAY_SIZE(c), cpio_srcbuf); 275 276 err = unpack_to_rootfs(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(c, ARRAY_SIZE(c), cpio_srcbuf); 285 286 err = unpack_to_rootfs(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(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(c, ARRAY_SIZE(c), cpio_srcbuf); 331 332 err = unpack_to_rootfs(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(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(thispath, "initramfs_test_many-%d", i); 371 p += fill_cpio(&c, 1, p); 372 } 373 374 len = p - cpio_srcbuf; 375 err = unpack_to_rootfs(cpio_srcbuf, len); 376 KUNIT_EXPECT_NULL(test, err); 377 378 for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) { 379 sprintf(thispath, "initramfs_test_many-%d", i); 380 KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0); 381 } 382 383 kfree(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