| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520 |
- // SPDX-License-Identifier: GPL-2.0
- #include <kunit/test.h>
- #include <linux/fcntl.h>
- #include <linux/file.h>
- #include <linux/fs.h>
- #include <linux/init_syscalls.h>
- #include <linux/stringify.h>
- #include <linux/timekeeping.h>
- #include "initramfs_internal.h"
- struct initramfs_test_cpio {
- char *magic;
- unsigned int ino;
- unsigned int mode;
- unsigned int uid;
- unsigned int gid;
- unsigned int nlink;
- unsigned int mtime;
- unsigned int filesize;
- unsigned int devmajor;
- unsigned int devminor;
- unsigned int rdevmajor;
- unsigned int rdevminor;
- unsigned int namesize;
- unsigned int csum;
- char *fname;
- char *data;
- };
- static size_t fill_cpio(struct initramfs_test_cpio *cs, size_t csz, char *out)
- {
- int i;
- size_t off = 0;
- for (i = 0; i < csz; i++) {
- char *pos = &out[off];
- struct initramfs_test_cpio *c = &cs[i];
- size_t thislen;
- /* +1 to account for nulterm */
- thislen = sprintf(pos, "%s"
- "%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x%08x"
- "%s",
- c->magic, c->ino, c->mode, c->uid, c->gid, c->nlink,
- c->mtime, c->filesize, c->devmajor, c->devminor,
- c->rdevmajor, c->rdevminor, c->namesize, c->csum,
- c->fname) + 1;
- pr_debug("packing (%zu): %.*s\n", thislen, (int)thislen, pos);
- if (thislen != CPIO_HDRLEN + c->namesize)
- pr_debug("padded to: %u\n", CPIO_HDRLEN + c->namesize);
- off += CPIO_HDRLEN + c->namesize;
- while (off & 3)
- out[off++] = '\0';
- memcpy(&out[off], c->data, c->filesize);
- off += c->filesize;
- while (off & 3)
- out[off++] = '\0';
- }
- return off;
- }
- static void __init initramfs_test_extract(struct kunit *test)
- {
- char *err, *cpio_srcbuf;
- size_t len;
- struct timespec64 ts_before, ts_after;
- struct kstat st = {};
- struct initramfs_test_cpio c[] = { {
- .magic = "070701",
- .ino = 1,
- .mode = S_IFREG | 0777,
- .uid = 12,
- .gid = 34,
- .nlink = 1,
- .mtime = 56,
- .filesize = 0,
- .devmajor = 0,
- .devminor = 1,
- .rdevmajor = 0,
- .rdevminor = 0,
- .namesize = sizeof("initramfs_test_extract"),
- .csum = 0,
- .fname = "initramfs_test_extract",
- }, {
- .magic = "070701",
- .ino = 2,
- .mode = S_IFDIR | 0777,
- .nlink = 1,
- .mtime = 57,
- .devminor = 1,
- .namesize = sizeof("initramfs_test_extract_dir"),
- .fname = "initramfs_test_extract_dir",
- }, {
- .magic = "070701",
- .namesize = sizeof("TRAILER!!!"),
- .fname = "TRAILER!!!",
- } };
- /* +3 to cater for any 4-byte end-alignment */
- cpio_srcbuf = kzalloc(ARRAY_SIZE(c) * (CPIO_HDRLEN + PATH_MAX + 3),
- GFP_KERNEL);
- len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
- ktime_get_real_ts64(&ts_before);
- err = unpack_to_rootfs(cpio_srcbuf, len);
- ktime_get_real_ts64(&ts_after);
- if (err) {
- KUNIT_FAIL(test, "unpack failed %s", err);
- goto out;
- }
- KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st, 0), 0);
- KUNIT_EXPECT_TRUE(test, S_ISREG(st.mode));
- KUNIT_EXPECT_TRUE(test, uid_eq(st.uid, KUIDT_INIT(c[0].uid)));
- KUNIT_EXPECT_TRUE(test, gid_eq(st.gid, KGIDT_INIT(c[0].gid)));
- KUNIT_EXPECT_EQ(test, st.nlink, 1);
- if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) {
- KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[0].mtime);
- } else {
- KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec);
- KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec);
- }
- KUNIT_EXPECT_EQ(test, st.blocks, c[0].filesize);
- KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st, 0), 0);
- KUNIT_EXPECT_TRUE(test, S_ISDIR(st.mode));
- if (IS_ENABLED(CONFIG_INITRAMFS_PRESERVE_MTIME)) {
- KUNIT_EXPECT_EQ(test, st.mtime.tv_sec, c[1].mtime);
- } else {
- KUNIT_EXPECT_GE(test, st.mtime.tv_sec, ts_before.tv_sec);
- KUNIT_EXPECT_LE(test, st.mtime.tv_sec, ts_after.tv_sec);
- }
- KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
- KUNIT_EXPECT_EQ(test, init_rmdir(c[1].fname), 0);
- out:
- kfree(cpio_srcbuf);
- }
- /*
- * Don't terminate filename. Previously, the cpio filename field was passed
- * directly to filp_open(collected, O_CREAT|..) without nulterm checks. See
- * https://lore.kernel.org/linux-fsdevel/20241030035509.20194-2-ddiss@suse.de
- */
- static void __init initramfs_test_fname_overrun(struct kunit *test)
- {
- char *err, *cpio_srcbuf;
- size_t len, suffix_off;
- struct initramfs_test_cpio c[] = { {
- .magic = "070701",
- .ino = 1,
- .mode = S_IFREG | 0777,
- .uid = 0,
- .gid = 0,
- .nlink = 1,
- .mtime = 1,
- .filesize = 0,
- .devmajor = 0,
- .devminor = 1,
- .rdevmajor = 0,
- .rdevminor = 0,
- .namesize = sizeof("initramfs_test_fname_overrun"),
- .csum = 0,
- .fname = "initramfs_test_fname_overrun",
- } };
- /*
- * poison cpio source buffer, so we can detect overrun. source
- * buffer is used by read_into() when hdr or fname
- * are already available (e.g. no compression).
- */
- cpio_srcbuf = kmalloc(CPIO_HDRLEN + PATH_MAX + 3, GFP_KERNEL);
- memset(cpio_srcbuf, 'B', CPIO_HDRLEN + PATH_MAX + 3);
- /* limit overrun to avoid crashes / filp_open() ENAMETOOLONG */
- cpio_srcbuf[CPIO_HDRLEN + strlen(c[0].fname) + 20] = '\0';
- len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
- /* overwrite trailing fname terminator and padding */
- suffix_off = len - 1;
- while (cpio_srcbuf[suffix_off] == '\0') {
- cpio_srcbuf[suffix_off] = 'P';
- suffix_off--;
- }
- err = unpack_to_rootfs(cpio_srcbuf, len);
- KUNIT_EXPECT_NOT_NULL(test, err);
- kfree(cpio_srcbuf);
- }
- static void __init initramfs_test_data(struct kunit *test)
- {
- char *err, *cpio_srcbuf;
- size_t len;
- struct file *file;
- struct initramfs_test_cpio c[] = { {
- .magic = "070701",
- .ino = 1,
- .mode = S_IFREG | 0777,
- .uid = 0,
- .gid = 0,
- .nlink = 1,
- .mtime = 1,
- .filesize = sizeof("ASDF") - 1,
- .devmajor = 0,
- .devminor = 1,
- .rdevmajor = 0,
- .rdevminor = 0,
- .namesize = sizeof("initramfs_test_data"),
- .csum = 0,
- .fname = "initramfs_test_data",
- .data = "ASDF",
- } };
- /* +6 for max name and data 4-byte padding */
- cpio_srcbuf = kmalloc(CPIO_HDRLEN + c[0].namesize + c[0].filesize + 6,
- GFP_KERNEL);
- len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
- err = unpack_to_rootfs(cpio_srcbuf, len);
- KUNIT_EXPECT_NULL(test, err);
- file = filp_open(c[0].fname, O_RDONLY, 0);
- if (IS_ERR(file)) {
- KUNIT_FAIL(test, "open failed");
- goto out;
- }
- /* read back file contents into @cpio_srcbuf and confirm match */
- len = kernel_read(file, cpio_srcbuf, c[0].filesize, NULL);
- KUNIT_EXPECT_EQ(test, len, c[0].filesize);
- KUNIT_EXPECT_MEMEQ(test, cpio_srcbuf, c[0].data, len);
- fput(file);
- KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
- out:
- kfree(cpio_srcbuf);
- }
- static void __init initramfs_test_csum(struct kunit *test)
- {
- char *err, *cpio_srcbuf;
- size_t len;
- struct initramfs_test_cpio c[] = { {
- /* 070702 magic indicates a valid csum is present */
- .magic = "070702",
- .ino = 1,
- .mode = S_IFREG | 0777,
- .nlink = 1,
- .filesize = sizeof("ASDF") - 1,
- .devminor = 1,
- .namesize = sizeof("initramfs_test_csum"),
- .csum = 'A' + 'S' + 'D' + 'F',
- .fname = "initramfs_test_csum",
- .data = "ASDF",
- }, {
- /* mix csum entry above with no-csum entry below */
- .magic = "070701",
- .ino = 2,
- .mode = S_IFREG | 0777,
- .nlink = 1,
- .filesize = sizeof("ASDF") - 1,
- .devminor = 1,
- .namesize = sizeof("initramfs_test_csum_not_here"),
- /* csum ignored */
- .csum = 5555,
- .fname = "initramfs_test_csum_not_here",
- .data = "ASDF",
- } };
- cpio_srcbuf = kmalloc(8192, GFP_KERNEL);
- len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
- err = unpack_to_rootfs(cpio_srcbuf, len);
- KUNIT_EXPECT_NULL(test, err);
- KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
- KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0);
- /* mess up the csum and confirm that unpack fails */
- c[0].csum--;
- len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
- err = unpack_to_rootfs(cpio_srcbuf, len);
- KUNIT_EXPECT_NOT_NULL(test, err);
- /*
- * file (with content) is still retained in case of bad-csum abort.
- * Perhaps we should change this.
- */
- KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
- KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), -ENOENT);
- kfree(cpio_srcbuf);
- }
- /*
- * hardlink hashtable may leak when the archive omits a trailer:
- * https://lore.kernel.org/r/20241107002044.16477-10-ddiss@suse.de/
- */
- static void __init initramfs_test_hardlink(struct kunit *test)
- {
- char *err, *cpio_srcbuf;
- size_t len;
- struct kstat st0, st1;
- struct initramfs_test_cpio c[] = { {
- .magic = "070701",
- .ino = 1,
- .mode = S_IFREG | 0777,
- .nlink = 2,
- .devminor = 1,
- .namesize = sizeof("initramfs_test_hardlink"),
- .fname = "initramfs_test_hardlink",
- }, {
- /* hardlink data is present in last archive entry */
- .magic = "070701",
- .ino = 1,
- .mode = S_IFREG | 0777,
- .nlink = 2,
- .filesize = sizeof("ASDF") - 1,
- .devminor = 1,
- .namesize = sizeof("initramfs_test_hardlink_link"),
- .fname = "initramfs_test_hardlink_link",
- .data = "ASDF",
- } };
- cpio_srcbuf = kmalloc(8192, GFP_KERNEL);
- len = fill_cpio(c, ARRAY_SIZE(c), cpio_srcbuf);
- err = unpack_to_rootfs(cpio_srcbuf, len);
- KUNIT_EXPECT_NULL(test, err);
- KUNIT_EXPECT_EQ(test, init_stat(c[0].fname, &st0, 0), 0);
- KUNIT_EXPECT_EQ(test, init_stat(c[1].fname, &st1, 0), 0);
- KUNIT_EXPECT_EQ(test, st0.ino, st1.ino);
- KUNIT_EXPECT_EQ(test, st0.nlink, 2);
- KUNIT_EXPECT_EQ(test, st1.nlink, 2);
- KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
- KUNIT_EXPECT_EQ(test, init_unlink(c[1].fname), 0);
- kfree(cpio_srcbuf);
- }
- #define INITRAMFS_TEST_MANY_LIMIT 1000
- #define INITRAMFS_TEST_MANY_PATH_MAX (sizeof("initramfs_test_many-") \
- + sizeof(__stringify(INITRAMFS_TEST_MANY_LIMIT)))
- static void __init initramfs_test_many(struct kunit *test)
- {
- char *err, *cpio_srcbuf, *p;
- size_t len = INITRAMFS_TEST_MANY_LIMIT *
- (CPIO_HDRLEN + INITRAMFS_TEST_MANY_PATH_MAX + 3);
- char thispath[INITRAMFS_TEST_MANY_PATH_MAX];
- int i;
- p = cpio_srcbuf = kmalloc(len, GFP_KERNEL);
- for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) {
- struct initramfs_test_cpio c = {
- .magic = "070701",
- .ino = i,
- .mode = S_IFREG | 0777,
- .nlink = 1,
- .devminor = 1,
- .fname = thispath,
- };
- c.namesize = 1 + sprintf(thispath, "initramfs_test_many-%d", i);
- p += fill_cpio(&c, 1, p);
- }
- len = p - cpio_srcbuf;
- err = unpack_to_rootfs(cpio_srcbuf, len);
- KUNIT_EXPECT_NULL(test, err);
- for (i = 0; i < INITRAMFS_TEST_MANY_LIMIT; i++) {
- sprintf(thispath, "initramfs_test_many-%d", i);
- KUNIT_EXPECT_EQ(test, init_unlink(thispath), 0);
- }
- kfree(cpio_srcbuf);
- }
- /*
- * An initramfs filename is namesize in length, including the zero-terminator.
- * A filename can be zero-terminated prior to namesize, with the remainder used
- * as padding. This can be useful for e.g. alignment of file data segments with
- * a 4KB filesystem block, allowing for extent sharing (reflinks) between cpio
- * source and destination. This hack works with both GNU cpio and initramfs, as
- * long as PATH_MAX isn't exceeded.
- */
- static void __init initramfs_test_fname_pad(struct kunit *test)
- {
- char *err;
- size_t len;
- struct file *file;
- char fdata[] = "this file data is aligned at 4K in the archive";
- struct test_fname_pad {
- char padded_fname[4096 - CPIO_HDRLEN];
- char cpio_srcbuf[CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)];
- } *tbufs = kzalloc_obj(struct test_fname_pad);
- struct initramfs_test_cpio c[] = { {
- .magic = "070701",
- .ino = 1,
- .mode = S_IFREG | 0777,
- .uid = 0,
- .gid = 0,
- .nlink = 1,
- .mtime = 1,
- .filesize = sizeof(fdata),
- .devmajor = 0,
- .devminor = 1,
- .rdevmajor = 0,
- .rdevminor = 0,
- /* align file data at 4K archive offset via padded fname */
- .namesize = 4096 - CPIO_HDRLEN,
- .csum = 0,
- .fname = tbufs->padded_fname,
- .data = fdata,
- } };
- memcpy(tbufs->padded_fname, "padded_fname", sizeof("padded_fname"));
- len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_srcbuf);
- err = unpack_to_rootfs(tbufs->cpio_srcbuf, len);
- KUNIT_EXPECT_NULL(test, err);
- file = filp_open(c[0].fname, O_RDONLY, 0);
- if (IS_ERR(file)) {
- KUNIT_FAIL(test, "open failed");
- goto out;
- }
- /* read back file contents into @cpio_srcbuf and confirm match */
- len = kernel_read(file, tbufs->cpio_srcbuf, c[0].filesize, NULL);
- KUNIT_EXPECT_EQ(test, len, c[0].filesize);
- KUNIT_EXPECT_MEMEQ(test, tbufs->cpio_srcbuf, c[0].data, len);
- fput(file);
- KUNIT_EXPECT_EQ(test, init_unlink(c[0].fname), 0);
- out:
- kfree(tbufs);
- }
- static void __init initramfs_test_fname_path_max(struct kunit *test)
- {
- char *err;
- size_t len;
- struct kstat st0, st1;
- char fdata[] = "this file data will not be unpacked";
- struct test_fname_path_max {
- char fname_oversize[PATH_MAX + 1];
- char fname_ok[PATH_MAX];
- char cpio_src[(CPIO_HDRLEN + PATH_MAX + 3 + sizeof(fdata)) * 2];
- } *tbufs = kzalloc_obj(struct test_fname_path_max);
- struct initramfs_test_cpio c[] = { {
- .magic = "070701",
- .ino = 1,
- .mode = S_IFDIR | 0777,
- .nlink = 1,
- .namesize = sizeof(tbufs->fname_oversize),
- .fname = tbufs->fname_oversize,
- .filesize = sizeof(fdata),
- .data = fdata,
- }, {
- .magic = "070701",
- .ino = 2,
- .mode = S_IFDIR | 0777,
- .nlink = 1,
- .namesize = sizeof(tbufs->fname_ok),
- .fname = tbufs->fname_ok,
- } };
- memset(tbufs->fname_oversize, '/', sizeof(tbufs->fname_oversize) - 1);
- memset(tbufs->fname_ok, '/', sizeof(tbufs->fname_ok) - 1);
- memcpy(tbufs->fname_oversize, "fname_oversize",
- sizeof("fname_oversize") - 1);
- memcpy(tbufs->fname_ok, "fname_ok", sizeof("fname_ok") - 1);
- len = fill_cpio(c, ARRAY_SIZE(c), tbufs->cpio_src);
- /* unpack skips over fname_oversize instead of returning an error */
- err = unpack_to_rootfs(tbufs->cpio_src, len);
- KUNIT_EXPECT_NULL(test, err);
- KUNIT_EXPECT_EQ(test, init_stat("fname_oversize", &st0, 0), -ENOENT);
- KUNIT_EXPECT_EQ(test, init_stat("fname_ok", &st1, 0), 0);
- KUNIT_EXPECT_EQ(test, init_rmdir("fname_ok"), 0);
- kfree(tbufs);
- }
- /*
- * The kunit_case/_suite struct cannot be marked as __initdata as this will be
- * used in debugfs to retrieve results after test has run.
- */
- static struct kunit_case __refdata initramfs_test_cases[] = {
- KUNIT_CASE(initramfs_test_extract),
- KUNIT_CASE(initramfs_test_fname_overrun),
- KUNIT_CASE(initramfs_test_data),
- KUNIT_CASE(initramfs_test_csum),
- KUNIT_CASE(initramfs_test_hardlink),
- KUNIT_CASE(initramfs_test_many),
- KUNIT_CASE(initramfs_test_fname_pad),
- KUNIT_CASE(initramfs_test_fname_path_max),
- {},
- };
- static struct kunit_suite initramfs_test_suite = {
- .name = "initramfs",
- .test_cases = initramfs_test_cases,
- };
- kunit_test_init_section_suites(&initramfs_test_suite);
- MODULE_DESCRIPTION("Initramfs KUnit test suite");
- MODULE_LICENSE("GPL v2");
|