This patchset is not yet complete, but it's already moving around a
bunch of stuff so I am sending it out to get either some agreement that
it's a vaguely sane approach, or some pointers about how I should be
doing this instead.
It aims to add an option to IMA to measure the individual components
that make up an initramfs that is being used for kexec, rather than the
entire initramfs blob. For example in the situation where the initramfs
blob contains some uncompressed early firmware and then a compressed
filesystem there will be 2 measurements folded into the TPM, and logged
into the IMA log.
Why is this useful? Consider the situation where images have been split
out to a set of firmware, an initial userspace image that does the usual
piece of finding the right root device and switching into it, and an
image that contains the necessary kernel modules.
For a given machine the firmware + userspace images are unlikely to
change often, while the kernel modules change with each upgrade. If we
measure the concatenated image as a single blob then it is necessary to
calculate all the permutations of images that result, which means
building and hashing the combinations. By measuring each piece
individually a hash can be calculated for each component up front
allowing for easier analysis of whether the running state is an expected
one.
The KEXEC_FILE_LOAD syscall only allows a single initramfs image to be
passed in; one option would be to add a new syscall that supports
multiple initramfs fds and read each in kimage_file_prepare_segments().
Instead I've taken a more complicated approach that doesn't involve a
new syscall or altering the kexec userspace, building on top of the way
the boot process parses the initramfs and using that same technique
within the IMA measurement for the READING_KEXEC_INITRAMFS path.
To that end I've pulled the cpio handling code out of init/initramfs.c
and into lib/ and made it usable outside of __init when required. That's
involved having to pull some of the init_syscall file handling routines
into the cpio code (and cleaning them up when the cpio code is the only
user). I think there's the potential for a bit more code clean up here,
but I've tried to keep it limited to providing the functionality I need
and making checkpatch happy for the moment.
Patch 1 pulls the code out to lib/ and moves the global static variables
that hold the state into a single context structure.
Patch 2 does some minimal error path improvements so we're not just
passing a string around to indicate there's been an error.
Patch 3 is where I pull the file handling routines into the cpio code.
It didn't seem worth moving this to somewhere other code could continue
to use them when only the cpio code was doing so, but it did involve a
few extra exported functions from fs/
Patch 4 actually allows the use of the cpio code outside of __init when
CONFIG_CPIO is selected.
Patch 5 is a hack so I can use the generic decompress + gzip outside of
__init. If this overall approach is acceptable then I'll do some work to
make this generically available in the same manner as the cpio code
before actually submitting for inclusion.
Patch 6 is the actual piece I'm interested in; doing individual
measurements for each component within IMA.
Jonathan McDowell (7):
initramfs: Move cpio handling routines into lib/
lib/cpio: Improve error handling
lib/cpio: use non __init filesystem related functions
lib/cpio: Allow use outside of initramfs creation
lib/cpio: Add a parse-only option that doesn't extract any files
HACK: Allow the use of generic decompress with gzip outside __init
ima: Support measurement of kexec initramfs components
fs/init.c | 101 -----
fs/internal.h | 4 -
include/linux/cpio.h | 91 +++++
include/linux/fs.h | 4 +
include/linux/init_syscalls.h | 6 -
init/initramfs.c | 522 +++----------------------
lib/Kconfig | 3 +
lib/Makefile | 2 +-
lib/cpio.c | 607 ++++++++++++++++++++++++++++++
lib/decompress.c | 4 +-
lib/decompress_inflate.c | 4 +
security/integrity/ima/Kconfig | 16 +
security/integrity/ima/ima_main.c | 191 +++++++++-
13 files changed, 961 insertions(+), 594 deletions(-)
create mode 100644 include/linux/cpio.h
create mode 100644 lib/cpio.c
--
2.36.1
On Fri, 2022-07-08 at 10:10 +0000, Jonathan McDowell wrote:
> This patchset is not yet complete, but it's already moving around a
> bunch of stuff so I am sending it out to get either some agreement that
> it's a vaguely sane approach, or some pointers about how I should be
> doing this instead.
>
> It aims to add an option to IMA to measure the individual components
> that make up an initramfs that is being used for kexec, rather than the
> entire initramfs blob. For example in the situation where the initramfs
> blob contains some uncompressed early firmware and then a compressed
> filesystem there will be 2 measurements folded into the TPM, and logged
> into the IMA log.
>
> Why is this useful? Consider the situation where images have been split
> out to a set of firmware, an initial userspace image that does the usual
> piece of finding the right root device and switching into it, and an
> image that contains the necessary kernel modules.
>
> For a given machine the firmware + userspace images are unlikely to
> change often, while the kernel modules change with each upgrade. If we
> measure the concatenated image as a single blob then it is necessary to
> calculate all the permutations of images that result, which means
> building and hashing the combinations. By measuring each piece
> individually a hash can be calculated for each component up front
> allowing for easier analysis of whether the running state is an expected
> one.
>
> The KEXEC_FILE_LOAD syscall only allows a single initramfs image to be
> passed in; one option would be to add a new syscall that supports
> multiple initramfs fds and read each in kimage_file_prepare_segments().
>
> Instead I've taken a more complicated approach that doesn't involve a
> new syscall or altering the kexec userspace, building on top of the way
> the boot process parses the initramfs and using that same technique
> within the IMA measurement for the READING_KEXEC_INITRAMFS path.
>
> To that end I've pulled the cpio handling code out of init/initramfs.c
> and into lib/ and made it usable outside of __init when required. That's
> involved having to pull some of the init_syscall file handling routines
> into the cpio code (and cleaning them up when the cpio code is the only
> user). I think there's the potential for a bit more code clean up here,
> but I've tried to keep it limited to providing the functionality I need
> and making checkpatch happy for the moment.
>
> Patch 1 pulls the code out to lib/ and moves the global static variables
> that hold the state into a single context structure.
>
> Patch 2 does some minimal error path improvements so we're not just
> passing a string around to indicate there's been an error.
>
> Patch 3 is where I pull the file handling routines into the cpio code.
> It didn't seem worth moving this to somewhere other code could continue
> to use them when only the cpio code was doing so, but it did involve a
> few extra exported functions from fs/
>
> Patch 4 actually allows the use of the cpio code outside of __init when
> CONFIG_CPIO is selected.
>
> Patch 5 is a hack so I can use the generic decompress + gzip outside of
> __init. If this overall approach is acceptable then I'll do some work to
> make this generically available in the same manner as the cpio code
> before actually submitting for inclusion.
>
> Patch 6 is the actual piece I'm interested in; doing individual
> measurements for each component within IMA.
Hi Jonathan,
Before going down this path, just making sure you're aware:
- of the IMA hooks for measuring and appraising firmware.
- of Roberto Sassu's "initramfs: add support for xattrs in the initial
ram disk" patch set that have been lingering for lack of review and
upstreaming.[1] There's been some recent interest in it.
[1] Message-Id: <[email protected]>
thanks,
Mimi
On Fri, Jul 08, 2022 at 07:49:58AM -0400, Mimi Zohar wrote:
> On Fri, 2022-07-08 at 10:10 +0000, Jonathan McDowell wrote:
> > This patchset is not yet complete, but it's already moving around a
> > bunch of stuff so I am sending it out to get either some agreement that
> > it's a vaguely sane approach, or some pointers about how I should be
> > doing this instead.
> >
> > It aims to add an option to IMA to measure the individual components
> > that make up an initramfs that is being used for kexec, rather than the
> > entire initramfs blob. For example in the situation where the initramfs
> > blob contains some uncompressed early firmware and then a compressed
> > filesystem there will be 2 measurements folded into the TPM, and logged
> > into the IMA log.
> >
> > Why is this useful? Consider the situation where images have been split
> > out to a set of firmware, an initial userspace image that does the usual
> > piece of finding the right root device and switching into it, and an
> > image that contains the necessary kernel modules.
> >
> > For a given machine the firmware + userspace images are unlikely to
> > change often, while the kernel modules change with each upgrade. If we
> > measure the concatenated image as a single blob then it is necessary to
> > calculate all the permutations of images that result, which means
> > building and hashing the combinations. By measuring each piece
> > individually a hash can be calculated for each component up front
> > allowing for easier analysis of whether the running state is an expected
> > one.
> >
> > The KEXEC_FILE_LOAD syscall only allows a single initramfs image to be
> > passed in; one option would be to add a new syscall that supports
> > multiple initramfs fds and read each in kimage_file_prepare_segments().
> >
> > Instead I've taken a more complicated approach that doesn't involve a
> > new syscall or altering the kexec userspace, building on top of the way
> > the boot process parses the initramfs and using that same technique
> > within the IMA measurement for the READING_KEXEC_INITRAMFS path.
> >
> > To that end I've pulled the cpio handling code out of init/initramfs.c
> > and into lib/ and made it usable outside of __init when required. That's
> > involved having to pull some of the init_syscall file handling routines
> > into the cpio code (and cleaning them up when the cpio code is the only
> > user). I think there's the potential for a bit more code clean up here,
> > but I've tried to keep it limited to providing the functionality I need
> > and making checkpatch happy for the moment.
> >
> > Patch 1 pulls the code out to lib/ and moves the global static variables
> > that hold the state into a single context structure.
> >
> > Patch 2 does some minimal error path improvements so we're not just
> > passing a string around to indicate there's been an error.
> >
> > Patch 3 is where I pull the file handling routines into the cpio code.
> > It didn't seem worth moving this to somewhere other code could continue
> > to use them when only the cpio code was doing so, but it did involve a
> > few extra exported functions from fs/
> >
> > Patch 4 actually allows the use of the cpio code outside of __init when
> > CONFIG_CPIO is selected.
> >
> > Patch 5 is a hack so I can use the generic decompress + gzip outside of
> > __init. If this overall approach is acceptable then I'll do some work to
> > make this generically available in the same manner as the cpio code
> > before actually submitting for inclusion.
> >
> > Patch 6 is the actual piece I'm interested in; doing individual
> > measurements for each component within IMA.
>
> Hi Jonathan,
>
> Before going down this path, just making sure you're aware:
> - of the IMA hooks for measuring and appraising firmware.
Yes, I'm aware of the FIRMWARE_CHECK hooks. This is more accurately
early stage firmware e.g. CPU microcode and it's not that we're
expecting this to load over a kexec but instead that the kernel /
initramfs loaded via kexec are what is also used for a traditional disk
boot, the kexec is just being used to shorten restart time. So although
the firmware isn't actually loaded it's part of the image and we'd like
to be able to keep the measurements for the parts separate.
> - of Roberto Sassu's "initramfs: add support for xattrs in the initial
> ram disk" patch set that have been lingering for lack of review and
> upstreaming.[1] There's been some recent interest in it.
>
> [1] Message-Id: <[email protected]>
That looks interesting, and obviously has some overlap in the areas I'm
touching, but I don't think it gives me the information I want from a
measurement perspective. The desire is that we can build a suitable
initramfs from a set of component building blocks, rather than a custom
image for each machine, and that we can measure the blocks rather than
the final result so that we just store the hash for each building block.
I think xattrs start to be more interesting when we extend to use some
sort of signing or fs-verity approach (which is in progress), but we'll
still want the measurement piece so we understand exactly what it is we
used to get to the current point in time.
J.
This patchset is not yet complete, but it's already moving around a
bunch of stuff so I am sending it out to get either some agreement that
it's a vaguely sane approach, or some pointers about how I should be
doing this instead. I've not had a lot of feedback from v1 but the
kernel test robot threw up a couple of compile failures and a boot
failure so this is a revised patch set with those fixed.
It aims to add an option to IMA to measure the individual components
that make up an initramfs that is being used for kexec, rather than the
entire initramfs blob. For example in the situation where the initramfs
blob contains some uncompressed early firmware and then a compressed
filesystem there will be 2 measurements folded into the TPM, and logged
into the IMA log.
Why is this useful? Consider the situation where images have been split
out to a set of firmware, an initial userspace image that does the usual
piece of finding the right root device and switching into it, and an
image that contains the necessary kernel modules.
For a given machine the firmware + userspace images are unlikely to
change often, while the kernel modules change with each upgrade. If we
measure the concatenated image as a single blob then it is necessary to
calculate all the permutations of images that result, which means
building and hashing the combinations. By measuring each piece
individually a hash can be calculated for each component up front
allowing for easier analysis of whether the running state is an expected
one.
The KEXEC_FILE_LOAD syscall only allows a single initramfs image to be
passed in; one option would be to add a new syscall that supports
multiple initramfs fds and read each in kimage_file_prepare_segments().
Instead I've taken a more complicated approach that doesn't involve a
new syscall or altering the kexec userspace, building on top of the way
the boot process parses the initramfs and using that same technique
within the IMA measurement for the READING_KEXEC_INITRAMFS path.
To that end I've pulled the cpio handling code out of init/initramfs.c
and into lib/ and made it usable outside of __init when required. That's
involved having to pull some of the init_syscall file handling routines
into the cpio code (and cleaning them up when the cpio code is the only
user). I think there's the potential for a bit more code clean up here,
but I've tried to keep it limited to providing the functionality I need
and making checkpatch happy for the moment.
Patch 1 pulls the code out to lib/ and moves the global static variables
that hold the state into a single context structure.
Patch 2 does some minimal error path improvements so we're not just
passing a string around to indicate there's been an error.
Patch 3 is where I pull the file handling routines into the cpio code.
It didn't seem worth moving this to somewhere other code could continue
to use them when only the cpio code was doing so, but it did involve a
few extra exported functions from fs/
Patch 4 actually allows the use of the cpio code outside of __init when
CONFIG_CPIO is selected.
Patch 5 is a hack so I can use the generic decompress + gzip outside of
__init. If this overall approach is acceptable then I'll do some work to
make this generically available in the same manner as the cpio code
before actually submitting for inclusion.
Patch 6 is the actual piece I'm interested in; doing individual
measurements for each component within IMA.
v2:
Fix printf format string in populate_initrd_image (kernel test robot, i386 build)
Include <linux/limits.h> in cpio.h (kernel test robot, uml build)
Fix EEXIST checking for device nodes (kernel test robot, boot attempt)
Jonathan McDowell (7):
initramfs: Move cpio handling routines into lib/
lib/cpio: Improve error handling
lib/cpio: use non __init filesystem related functions
lib/cpio: Allow use outside of initramfs creation
lib/cpio: Add a parse-only option that doesn't extract any files
HACK: Allow the use of generic decompress with gzip outside __init
ima: Support measurement of kexec initramfs components
fs/init.c | 101 -----
fs/internal.h | 4 -
include/linux/cpio.h | 92 +++++
include/linux/fs.h | 4 +
include/linux/init_syscalls.h | 6 -
init/initramfs.c | 524 +++----------------------
lib/Kconfig | 3 +
lib/Makefile | 2 +-
lib/cpio.c | 609 ++++++++++++++++++++++++++++++
lib/decompress.c | 4 +-
lib/decompress_inflate.c | 4 +
security/integrity/ima/Kconfig | 16 +
security/integrity/ima/ima_main.c | 191 +++++++++-
13 files changed, 965 insertions(+), 595 deletions(-)
create mode 100644 include/linux/cpio.h
create mode 100644 lib/cpio.c
--
2.30.2
Now we no longer depend on anything that lives in __init add a Kconfig
option to allow the cpio code to be used by other code within the
kernel. If not selected the code will continue to be placed within the
__init section and discarded after boot.
Signed-off-by: Jonathan McDowell <[email protected]>
---
v2:
- Move directory EEXIST checking to patch 3
---
include/linux/cpio.h | 20 ++++++++++++---
lib/Kconfig | 3 +++
lib/cpio.c | 60 ++++++++++++++++++++++----------------------
3 files changed, 49 insertions(+), 34 deletions(-)
diff --git a/include/linux/cpio.h b/include/linux/cpio.h
index 69a15fffa5c6..7e9888e6a1ad 100644
--- a/include/linux/cpio.h
+++ b/include/linux/cpio.h
@@ -12,6 +12,18 @@
#define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
+/*
+ * If nothing explicitly wants us then we can live in the __init section as
+ * only the initramfs code will call us.
+ */
+#ifdef CONFIG_CPIO
+#define __cpio
+#define __cpiodata
+#else
+#define __cpio __init
+#define __cpiodata __initdata
+#endif
+
enum cpio_state {
CPIO_START,
CPIO_COLLECT,
@@ -68,11 +80,11 @@ struct cpio_context {
struct list_head dir_list;
};
-int __init cpio_start(struct cpio_context *ctx);
-void __init cpio_finish(struct cpio_context *ctx);
-long __init cpio_write_buffer(struct cpio_context *ctx, char *buf,
+int __cpio cpio_start(struct cpio_context *ctx);
+void __cpio cpio_finish(struct cpio_context *ctx);
+long __cpio cpio_write_buffer(struct cpio_context *ctx, char *buf,
unsigned long len);
-long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
+long __cpio cpio_process_buffer(struct cpio_context *ctx, void *bufv,
unsigned long len);
#endif /* _LINUX_CPIO_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index eaaad4d85bf2..fad66ee4caed 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -743,3 +743,6 @@ config ASN1_ENCODER
config POLYNOMIAL
tristate
+
+config CPIO
+ bool
diff --git a/lib/cpio.c b/lib/cpio.c
index 9a0120c638db..03967e063c76 100644
--- a/lib/cpio.c
+++ b/lib/cpio.c
@@ -8,7 +8,7 @@
#include <linux/security.h>
#include <linux/slab.h>
-static ssize_t __init xwrite(struct cpio_context *ctx, struct file *file,
+static ssize_t __cpio xwrite(struct cpio_context *ctx, struct file *file,
const unsigned char *p, size_t count, loff_t *pos)
{
ssize_t out = 0;
@@ -50,7 +50,7 @@ static inline int hash(int major, int minor, int ino)
return tmp & (CPIO_LINK_HASH_SIZE - 1);
}
-static char __init *find_link(struct cpio_context *ctx, int major, int minor,
+static char __cpio *find_link(struct cpio_context *ctx, int major, int minor,
int ino, umode_t mode, char *name)
{
struct cpio_link_hash **p, *q;
@@ -79,7 +79,7 @@ static char __init *find_link(struct cpio_context *ctx, int major, int minor,
return NULL;
}
-static void __init free_hash(struct cpio_context *ctx)
+static void __cpio free_hash(struct cpio_context *ctx)
{
struct cpio_link_hash **p, *q;
@@ -93,14 +93,14 @@ static void __init free_hash(struct cpio_context *ctx)
}
#ifdef CONFIG_INITRAMFS_PRESERVE_MTIME
-static void __init do_utime_path(const struct path *path, time64_t mtime)
+static void __cpio do_utime_path(const struct path *path, time64_t mtime)
{
struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
vfs_utimes(path, t);
}
-static int __init do_utime(char *filename, time64_t mtime)
+static int __cpio do_utime(char *filename, time64_t mtime)
{
struct path path;
int error;
@@ -114,7 +114,7 @@ static int __init do_utime(char *filename, time64_t mtime)
return error;
}
-static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t mtime)
+static int __cpio dir_add(struct cpio_context *ctx, const char *name, time64_t mtime)
{
size_t nlen = strlen(name) + 1;
struct cpio_dir_entry *de;
@@ -130,7 +130,7 @@ static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t m
return 0;
}
-static void __init dir_utime(struct cpio_context *ctx)
+static void __cpio dir_utime(struct cpio_context *ctx)
{
struct cpio_dir_entry *de, *tmp;
@@ -141,13 +141,13 @@ static void __init dir_utime(struct cpio_context *ctx)
}
}
#else
-static int __init do_utime(char *filename, time64_t mtime) { return 0; }
-static void __init do_utime_path(const struct path *path, time64_t mtime) {}
-static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t mtime) { return 0; }
-static void __init dir_utime(struct cpio_context *ctx) {}
+static int __cpio do_utime(char *filename, time64_t mtime) { return 0; }
+static void __cpio do_utime_path(const struct path *path, time64_t mtime) {}
+static int __cpio dir_add(struct cpio_context *ctx, const char *name, time64_t mtime) { return 0; }
+static void __cpio dir_utime(struct cpio_context *ctx) {}
#endif
-static int __init cpio_chown(const char *filename, uid_t user, gid_t group,
+static int __cpio cpio_chown(const char *filename, uid_t user, gid_t group,
int flags)
{
int lookup_flags = (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
@@ -168,7 +168,7 @@ static int __init cpio_chown(const char *filename, uid_t user, gid_t group,
/* cpio header parsing */
-static void __init parse_header(struct cpio_context *ctx, char *s)
+static void __cpio parse_header(struct cpio_context *ctx, char *s)
{
unsigned long parsed[13];
char buf[9];
@@ -195,14 +195,14 @@ static void __init parse_header(struct cpio_context *ctx, char *s)
/* FSM */
-static inline void __init eat(struct cpio_context *ctx, unsigned int n)
+static inline void __cpio eat(struct cpio_context *ctx, unsigned int n)
{
ctx->victim += n;
ctx->this_header += n;
ctx->byte_count -= n;
}
-static void __init read_into(struct cpio_context *ctx, char *buf,
+static void __cpio read_into(struct cpio_context *ctx, char *buf,
unsigned int size, enum cpio_state next)
{
if (ctx->byte_count >= size) {
@@ -218,13 +218,13 @@ static void __init read_into(struct cpio_context *ctx, char *buf,
}
}
-static int __init do_start(struct cpio_context *ctx)
+static int __cpio do_start(struct cpio_context *ctx)
{
read_into(ctx, ctx->header_buf, 110, CPIO_GOTHEADER);
return 0;
}
-static int __init do_collect(struct cpio_context *ctx)
+static int __cpio do_collect(struct cpio_context *ctx)
{
unsigned long n = ctx->remains;
@@ -242,7 +242,7 @@ static int __init do_collect(struct cpio_context *ctx)
return 0;
}
-static int __init do_header(struct cpio_context *ctx)
+static int __cpio do_header(struct cpio_context *ctx)
{
if (!memcmp(ctx->collected, "070701", 6)) {
ctx->csum_present = false;
@@ -274,7 +274,7 @@ static int __init do_header(struct cpio_context *ctx)
return 0;
}
-static int __init do_skip(struct cpio_context *ctx)
+static int __cpio do_skip(struct cpio_context *ctx)
{
if (ctx->this_header + ctx->byte_count < ctx->next_header) {
eat(ctx, ctx->byte_count);
@@ -286,7 +286,7 @@ static int __init do_skip(struct cpio_context *ctx)
return 0;
}
-static int __init do_reset(struct cpio_context *ctx)
+static int __cpio do_reset(struct cpio_context *ctx)
{
while (ctx->byte_count && *ctx->victim == '\0')
eat(ctx, 1);
@@ -296,7 +296,7 @@ static int __init do_reset(struct cpio_context *ctx)
return 1;
}
-static void __init clean_path(char *pathname, umode_t fmode)
+static void __cpio clean_path(char *pathname, umode_t fmode)
{
struct path path;
struct kstat st;
@@ -318,7 +318,7 @@ static void __init clean_path(char *pathname, umode_t fmode)
}
}
-static int __init maybe_link(struct cpio_context *ctx)
+static int __cpio maybe_link(struct cpio_context *ctx)
{
struct dentry *new_dentry;
struct path old_path, new_path;
@@ -362,7 +362,7 @@ static int __init maybe_link(struct cpio_context *ctx)
return 0;
}
-static int __init do_name(struct cpio_context *ctx)
+static int __cpio do_name(struct cpio_context *ctx)
{
struct dentry *dentry;
struct path path;
@@ -444,7 +444,7 @@ static int __init do_name(struct cpio_context *ctx)
return 0;
}
-static int __init do_copy(struct cpio_context *ctx)
+static int __cpio do_copy(struct cpio_context *ctx)
{
int ret;
@@ -474,7 +474,7 @@ static int __init do_copy(struct cpio_context *ctx)
return 1;
}
-static int __init do_symlink(struct cpio_context *ctx)
+static int __cpio do_symlink(struct cpio_context *ctx)
{
struct dentry *dentry;
struct path path;
@@ -503,7 +503,7 @@ static int __init do_symlink(struct cpio_context *ctx)
return 0;
}
-static __initdata int (*actions[])(struct cpio_context *) = {
+static __cpiodata int (*actions[])(struct cpio_context *) = {
[CPIO_START] = do_start,
[CPIO_COLLECT] = do_collect,
[CPIO_GOTHEADER] = do_header,
@@ -514,7 +514,7 @@ static __initdata int (*actions[])(struct cpio_context *) = {
[CPIO_RESET] = do_reset,
};
-long __init cpio_write_buffer(struct cpio_context *ctx, char *buf,
+long __cpio cpio_write_buffer(struct cpio_context *ctx, char *buf,
unsigned long len)
{
int ret;
@@ -532,7 +532,7 @@ long __init cpio_write_buffer(struct cpio_context *ctx, char *buf,
return len - ctx->byte_count;
}
-long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
+long __cpio cpio_process_buffer(struct cpio_context *ctx, void *bufv,
unsigned long len)
{
char *buf = (char *)bufv;
@@ -563,7 +563,7 @@ long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
return len;
}
-int __init cpio_start(struct cpio_context *ctx)
+int __cpio cpio_start(struct cpio_context *ctx)
{
ctx->header_buf = kmalloc(110, GFP_KERNEL);
ctx->symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
@@ -579,7 +579,7 @@ int __init cpio_start(struct cpio_context *ctx)
return 0;
}
-void __init cpio_finish(struct cpio_context *ctx)
+void __cpio cpio_finish(struct cpio_context *ctx)
{
dir_utime(ctx);
kfree(ctx->name_buf);
--
2.30.2
In order to allow a CPIO archive to be parsed without actually
extracting anything add a parse_only flag.
Signed-off-by: Jonathan McDowell <[email protected]>
---
include/linux/cpio.h | 2 ++
lib/cpio.c | 35 ++++++++++++++++++++++++++++-------
2 files changed, 30 insertions(+), 7 deletions(-)
diff --git a/include/linux/cpio.h b/include/linux/cpio.h
index 7e9888e6a1ad..5b897d1d3143 100644
--- a/include/linux/cpio.h
+++ b/include/linux/cpio.h
@@ -78,6 +78,8 @@ struct cpio_context {
struct cpio_link_hash *link_hash[CPIO_LINK_HASH_SIZE];
struct list_head dir_list;
+
+ bool parse_only;
};
int __cpio cpio_start(struct cpio_context *ctx);
diff --git a/lib/cpio.c b/lib/cpio.c
index 03967e063c76..37e2e2071c8d 100644
--- a/lib/cpio.c
+++ b/lib/cpio.c
@@ -15,7 +15,12 @@ static ssize_t __cpio xwrite(struct cpio_context *ctx, struct file *file,
/* sys_write only can write MAX_RW_COUNT aka 2G-4K bytes at most */
while (count) {
- ssize_t rv = kernel_write(file, p, count, pos);
+ ssize_t rv;
+
+ if (ctx->parse_only)
+ rv = count;
+ else
+ rv = kernel_write(file, p, count, pos);
if (rv < 0) {
if (rv == -EINTR || rv == -EAGAIN)
@@ -136,7 +141,8 @@ static void __cpio dir_utime(struct cpio_context *ctx)
list_for_each_entry_safe(de, tmp, &ctx->dir_list, list) {
list_del(&de->list);
- do_utime(de->name, de->mtime);
+ if (!ctx->parse_only)
+ do_utime(de->name, de->mtime);
kfree(de);
}
}
@@ -374,6 +380,13 @@ static int __cpio do_name(struct cpio_context *ctx)
free_hash(ctx);
return 0;
}
+
+ if (ctx->parse_only) {
+ if (S_ISREG(ctx->mode))
+ ctx->state = CPIO_COPYFILE;
+ return 0;
+ }
+
clean_path(ctx->collected, ctx->mode);
if (S_ISREG(ctx->mode)) {
int ml = maybe_link(ctx);
@@ -454,8 +467,10 @@ static int __cpio do_copy(struct cpio_context *ctx)
if (ret != ctx->body_len)
return (ret < 0) ? ret : -EIO;
- do_utime_path(&ctx->wfile->f_path, ctx->mtime);
- fput(ctx->wfile);
+ if (!ctx->parse_only) {
+ do_utime_path(&ctx->wfile->f_path, ctx->mtime);
+ fput(ctx->wfile);
+ }
if (ctx->csum_present && ctx->io_csum != ctx->hdr_csum)
return -EBADMSG;
@@ -480,6 +495,12 @@ static int __cpio do_symlink(struct cpio_context *ctx)
struct path path;
int error;
+ ctx->state = CPIO_SKIPIT;
+ ctx->next_state = CPIO_RESET;
+
+ if (ctx->parse_only)
+ return 0;
+
ctx->collected[N_ALIGN(ctx->name_len) + ctx->body_len] = '\0';
clean_path(ctx->collected, 0);
@@ -498,8 +519,7 @@ static int __cpio do_symlink(struct cpio_context *ctx)
cpio_chown(ctx->collected, ctx->uid, ctx->gid, AT_SYMLINK_NOFOLLOW);
do_utime(ctx->collected, ctx->mtime);
- ctx->state = CPIO_SKIPIT;
- ctx->next_state = CPIO_RESET;
+
return 0;
}
@@ -581,7 +601,8 @@ int __cpio cpio_start(struct cpio_context *ctx)
void __cpio cpio_finish(struct cpio_context *ctx)
{
- dir_utime(ctx);
+ if (!ctx->parse_only)
+ dir_utime(ctx);
kfree(ctx->name_buf);
kfree(ctx->symlink_buf);
kfree(ctx->header_buf);
--
2.30.2
As preparation for making the cpio parsing routines more generally
available improve the error handling such that we pass back a suitable
errno rather than a string message, and correctly exit execution when
such an error is raised.
Signed-off-by: Jonathan McDowell <[email protected]>
---
include/linux/cpio.h | 1 -
init/initramfs.c | 10 ++++++--
lib/cpio.c | 56 +++++++++++++++++++++++++++-----------------
3 files changed, 43 insertions(+), 24 deletions(-)
diff --git a/include/linux/cpio.h b/include/linux/cpio.h
index 2f9fd735331e..69a15fffa5c6 100644
--- a/include/linux/cpio.h
+++ b/include/linux/cpio.h
@@ -44,7 +44,6 @@ struct cpio_context {
unsigned long byte_count;
bool csum_present;
u32 io_csum;
- char *errmsg;
char *collected;
long remains;
diff --git a/init/initramfs.c b/init/initramfs.c
index 00c101d04f4b..79c3a3f42cdb 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -62,10 +62,16 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
if (*buf == '0' && !(ctx.this_header & 3)) {
ctx.state = CPIO_START;
written = cpio_write_buffer(&ctx, buf, len);
+
+ if (written < 0) {
+ pr_err("Failed to process archive: %ld\n",
+ written);
+ error("failed to process archive");
+ break;
+ }
+
buf += written;
len -= written;
- if (ctx.errmsg)
- message = ctx.errmsg;
continue;
}
if (!*buf) {
diff --git a/lib/cpio.c b/lib/cpio.c
index c71bebd4cc98..5d150939704f 100644
--- a/lib/cpio.c
+++ b/lib/cpio.c
@@ -221,12 +221,10 @@ static int __init do_header(struct cpio_context *ctx)
ctx->csum_present = false;
} else if (!memcmp(ctx->collected, "070702", 6)) {
ctx->csum_present = true;
+ } else if (memcmp(ctx->collected, "070707", 6) == 0) {
+ return -EPROTONOSUPPORT;
} else {
- if (memcmp(ctx->collected, "070707", 6) == 0)
- ctx->errmsg = "incorrect cpio method used: use -H newc option";
- else
- ctx->errmsg = "no cpio magic";
- return 1;
+ return -EINVAL;
}
parse_header(ctx, ctx->collected);
ctx->next_header = ctx->this_header + N_ALIGN(ctx->name_len) + ctx->body_len;
@@ -266,7 +264,8 @@ static int __init do_reset(struct cpio_context *ctx)
while (ctx->byte_count && *ctx->victim == '\0')
eat(ctx, 1);
if (ctx->byte_count && (ctx->this_header & 3))
- ctx->errmsg = "broken padding";
+ return -EFAULT;
+
return 1;
}
@@ -344,23 +343,29 @@ static int __init do_name(struct cpio_context *ctx)
static int __init do_copy(struct cpio_context *ctx)
{
+ int ret;
+
if (ctx->byte_count >= ctx->body_len) {
- if (xwrite(ctx, ctx->wfile, ctx->victim, ctx->body_len,
- &ctx->wfile_pos) != ctx->body_len)
- ctx->errmsg = "write error";
+ ret = xwrite(ctx, ctx->wfile, ctx->victim, ctx->body_len,
+ &ctx->wfile_pos);
+ if (ret != ctx->body_len)
+ return (ret < 0) ? ret : -EIO;
do_utime_path(&ctx->wfile->f_path, ctx->mtime);
fput(ctx->wfile);
if (ctx->csum_present && ctx->io_csum != ctx->hdr_csum)
- ctx->errmsg = "bad data checksum";
+ return -EBADMSG;
+
eat(ctx, ctx->body_len);
ctx->state = CPIO_SKIPIT;
return 0;
}
- if (xwrite(ctx, ctx->wfile, ctx->victim, ctx->byte_count,
- &ctx->wfile_pos) != ctx->byte_count)
- ctx->errmsg = "write error";
+ ret = xwrite(ctx, ctx->wfile, ctx->victim, ctx->byte_count,
+ &ctx->wfile_pos);
+ if (ret != ctx->byte_count)
+ return (ret < 0) ? ret : -EIO;
+
ctx->body_len -= ctx->byte_count;
eat(ctx, ctx->byte_count);
return 1;
@@ -392,12 +397,19 @@ static __initdata int (*actions[])(struct cpio_context *) = {
long __init cpio_write_buffer(struct cpio_context *ctx, char *buf,
unsigned long len)
{
+ int ret;
+
ctx->byte_count = len;
ctx->victim = buf;
- while (!actions[ctx->state](ctx))
- ;
- return len - ctx->byte_count;
+ ret = 0;
+ while (ret == 0)
+ ret = actions[ctx->state](ctx);
+
+ if (ret < 0)
+ return ret;
+ else
+ return len - ctx->byte_count;
}
long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
@@ -407,11 +419,13 @@ long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
long written;
long left = len;
- if (ctx->errmsg)
- return -1;
+ while ((written = cpio_write_buffer(ctx, buf, left)) < left) {
+ char c;
+
+ if (written < 0)
+ return written;
- while ((written = cpio_write_buffer(ctx, buf, left)) < left && !ctx->errmsg) {
- char c = buf[written];
+ c = buf[written];
if (c == '0') {
buf += written;
@@ -422,7 +436,7 @@ long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
left -= written;
ctx->state = CPIO_RESET;
} else {
- ctx->errmsg = "junk within compressed archive";
+ return -EINVAL;
}
}
--
2.30.2
An initramfs can be made up of multiple components that are concatenated
together e.g. an early uncompressed cpio archive containing early
firmware followed by a gziped cpio archive containing the actual
userspace initramfs. Add a Kconfig option to allow the IMA subsystem to
measure these components separately rather than as a single blob,
allowing for easier reasoning about system state when checking TPM PCR
values or the IMA integrity log.
Signed-off-by: Jonathan McDowell <[email protected]>
---
security/integrity/ima/Kconfig | 16 +++
security/integrity/ima/ima_main.c | 191 ++++++++++++++++++++++++++++--
2 files changed, 199 insertions(+), 8 deletions(-)
diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig
index 7249f16257c7..b75da44a32f2 100644
--- a/security/integrity/ima/Kconfig
+++ b/security/integrity/ima/Kconfig
@@ -41,6 +41,22 @@ config IMA_KEXEC
Depending on the IMA policy, the measurement list can grow to
be very large.
+config IMA_MEASURE_INITRAMFS_COMPONENTS
+ bool "Enable measurement of individual kexec initramfs components"
+ depends on IMA
+ select CPIO
+ default n
+ help
+ initramfs images can be made up of multiple separate components,
+ e.g. an early uncompressed cpio archive containing early firmware
+ followed by a gziped cpio archive containing the actual userspace
+ initramfs. More complex systems might involve a firmware archive,
+ a userspace archive and then a kernel module archive, allowing for
+ only the piece that needs changed to vary between boots.
+
+ This option tells IMA to measure each individual component of the
+ initramfs separately, rather than as a single blob.
+
config IMA_MEASURE_PCR_IDX
int
depends on IMA
diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
index 040b03ddc1c7..be7f446df4f2 100644
--- a/security/integrity/ima/ima_main.c
+++ b/security/integrity/ima/ima_main.c
@@ -26,6 +26,8 @@
#include <linux/ima.h>
#include <linux/iversion.h>
#include <linux/fs.h>
+#include <linux/cpio.h>
+#include <linux/decompress/generic.h>
#include "ima.h"
@@ -198,6 +200,169 @@ void ima_file_free(struct file *file)
ima_check_last_writer(iint, inode, file);
}
+#ifdef CONFIG_IMA_MEASURE_INITRAMFS_COMPONENTS
+static void initrd_error(char *x)
+{
+ pr_err("measure initrd: error from decompressor: %s\n", x);
+}
+
+static long initrd_flush(void *buf, unsigned long size)
+{
+ return size;
+}
+
+static int process_initrd_measurement(struct integrity_iint_cache *iint,
+ struct file *file, char *buf,
+ loff_t size, const char *pathname,
+ struct modsig *modsig, int pcr,
+ struct evm_ima_xattr_data *xattr_value,
+ int xattr_len,
+ struct ima_template_desc *template_desc)
+{
+ struct cpio_context cpio_ctx;
+ const char *compress_name;
+ enum hash_algo hash_algo;
+ decompress_fn decompress;
+ long consumed, written;
+ char *start, *cur;
+ char *component;
+ int buf_len;
+ bool in_cpio;
+ int rc = 0;
+ int part;
+
+ /*
+ * We collect this once, over the whole buffer.
+ */
+ if (modsig)
+ ima_collect_modsig(modsig, buf, size);
+
+ hash_algo = ima_get_hash_algo(xattr_value, xattr_len);
+
+ /*
+ * Pathname, compression name, 2 : separators, single digit part
+ * and a trailing NUL.
+ */
+ buf_len = strlen(pathname) + 5 + 2 + 2;
+ component = kmalloc(buf_len, GFP_KERNEL);
+ if (!component)
+ return -ENOMEM;
+
+ memset(&cpio_ctx, 0, sizeof(cpio_ctx));
+ cpio_ctx.parse_only = true;
+ rc = cpio_start(&cpio_ctx);
+ if (rc)
+ goto out;
+ in_cpio = false;
+ start = buf;
+ cur = buf;
+ part = 0;
+
+ while (rc == 0 && size) {
+ loff_t saved_offset = cpio_ctx.this_header;
+
+ /* It's a CPIO archive, process it */
+ if (*buf == '0' && !(cpio_ctx.this_header & 3)) {
+ in_cpio = true;
+ cpio_ctx.state = CPIO_START;
+ written = cpio_write_buffer(&cpio_ctx, buf, size);
+
+ if (written < 0) {
+ pr_err("Failed to process archive: %ld\n",
+ written);
+ break;
+ }
+
+ buf += written;
+ size -= written;
+ continue;
+ }
+ if (!*buf) {
+ buf++;
+ size--;
+ cpio_ctx.this_header++;
+ continue;
+ }
+
+ if (in_cpio) {
+ iint->flags &= ~(IMA_COLLECTED);
+ iint->measured_pcrs &= ~(0x1 << pcr);
+ rc = ima_collect_measurement(iint, file, cur,
+ buf - cur, hash_algo,
+ NULL);
+ if (rc == -ENOMEM)
+ return rc;
+
+ snprintf(component, buf_len, "%s:%s:%d",
+ pathname, "cpio", part);
+
+ ima_store_measurement(iint, file, component,
+ xattr_value, xattr_len, NULL, pcr,
+ template_desc);
+ part++;
+
+ in_cpio = false;
+ }
+
+ decompress = decompress_method(buf, size, &compress_name);
+ if (decompress) {
+ rc = decompress(buf, size, NULL, initrd_flush, NULL,
+ &consumed, initrd_error);
+ if (rc) {
+ pr_err("Failed to decompress archive\n");
+ break;
+ }
+ } else if (compress_name) {
+ pr_info("Compression method %s not configured.\n", compress_name);
+ break;
+ }
+
+ iint->flags &= ~(IMA_COLLECTED);
+ iint->measured_pcrs &= ~(0x1 << pcr);
+ rc = ima_collect_measurement(iint, file, buf,
+ consumed, hash_algo, NULL);
+ if (rc == -ENOMEM)
+ goto out;
+
+ snprintf(component, buf_len, "%s:%s:%d", pathname,
+ compress_name, part);
+
+ ima_store_measurement(iint, file, component,
+ xattr_value, xattr_len, NULL, pcr,
+ template_desc);
+ part++;
+
+ cpio_ctx.this_header = saved_offset + consumed;
+ buf += consumed;
+ size -= consumed;
+ cur = buf;
+ }
+ cpio_finish(&cpio_ctx);
+
+ /* Measure anything that remains */
+ if (size != 0) {
+ iint->flags &= ~(IMA_COLLECTED);
+ iint->measured_pcrs &= ~(0x1 << pcr);
+ rc = ima_collect_measurement(iint, file, buf, size, hash_algo,
+ NULL);
+ if (rc == -ENOMEM)
+ goto out;
+
+ snprintf(component, buf_len, "%s:left:%d",
+ pathname,
+ part);
+
+ ima_store_measurement(iint, file, component,
+ xattr_value, xattr_len, NULL, pcr,
+ template_desc);
+ }
+
+out:
+ kfree(component);
+ return rc;
+}
+#endif
+
static int process_measurement(struct file *file, const struct cred *cred,
u32 secid, char *buf, loff_t size, int mask,
enum ima_hooks func)
@@ -334,17 +499,27 @@ static int process_measurement(struct file *file, const struct cred *cred,
hash_algo = ima_get_hash_algo(xattr_value, xattr_len);
- rc = ima_collect_measurement(iint, file, buf, size, hash_algo, modsig);
- if (rc == -ENOMEM)
- goto out_locked;
-
if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */
pathname = ima_d_path(&file->f_path, &pathbuf, filename);
- if (action & IMA_MEASURE)
- ima_store_measurement(iint, file, pathname,
- xattr_value, xattr_len, modsig, pcr,
- template_desc);
+ if (IS_ENABLED(CONFIG_IMA_MEASURE_INITRAMFS_COMPONENTS) &&
+ (action & IMA_MEASURE) && func == KEXEC_INITRAMFS_CHECK) {
+ rc = process_initrd_measurement(iint, file, buf, size,
+ pathname, modsig, pcr,
+ xattr_value, xattr_len,
+ template_desc);
+ } else {
+ rc = ima_collect_measurement(iint, file, buf, size, hash_algo,
+ modsig);
+ if (rc == -ENOMEM)
+ goto out_locked;
+
+ if (action & IMA_MEASURE)
+ ima_store_measurement(iint, file, pathname,
+ xattr_value, xattr_len, modsig,
+ pcr, template_desc);
+ }
+
if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) {
rc = ima_check_blacklist(iint, modsig, pcr);
if (rc != -EPERM) {
--
2.30.2
In preparation for making the cpio functions generally available rather
than just at init make sure we're using versions of the filesystem
related functions that aren't in the __init section. Remove functions
only used by us from fs/init.c while folding into the cpio code
directly.
Signed-off-by: Jonathan McDowell <[email protected]>
---
v2:
- Fold in directory EEXIST checking from patch 4
- Add EEXIST checking for device nodes (kernel test reboot boot test)
---
fs/init.c | 101 ---------------------
fs/internal.h | 4 -
include/linux/fs.h | 4 +
include/linux/init_syscalls.h | 6 --
lib/cpio.c | 162 +++++++++++++++++++++++++++++-----
5 files changed, 145 insertions(+), 132 deletions(-)
diff --git a/fs/init.c b/fs/init.c
index 5c36adaa9b44..a946ad672dee 100644
--- a/fs/init.c
+++ b/fs/init.c
@@ -79,37 +79,6 @@ int __init init_chroot(const char *filename)
return error;
}
-int __init init_chown(const char *filename, uid_t user, gid_t group, int flags)
-{
- int lookup_flags = (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
- struct path path;
- int error;
-
- error = kern_path(filename, lookup_flags, &path);
- if (error)
- return error;
- error = mnt_want_write(path.mnt);
- if (!error) {
- error = chown_common(&path, user, group);
- mnt_drop_write(path.mnt);
- }
- path_put(&path);
- return error;
-}
-
-int __init init_chmod(const char *filename, umode_t mode)
-{
- struct path path;
- int error;
-
- error = kern_path(filename, LOOKUP_FOLLOW, &path);
- if (error)
- return error;
- error = chmod_common(&path, mode);
- path_put(&path);
- return error;
-}
-
int __init init_eaccess(const char *filename)
{
struct path path;
@@ -163,58 +132,6 @@ int __init init_mknod(const char *filename, umode_t mode, unsigned int dev)
return error;
}
-int __init init_link(const char *oldname, const char *newname)
-{
- struct dentry *new_dentry;
- struct path old_path, new_path;
- struct user_namespace *mnt_userns;
- int error;
-
- error = kern_path(oldname, 0, &old_path);
- if (error)
- return error;
-
- new_dentry = kern_path_create(AT_FDCWD, newname, &new_path, 0);
- error = PTR_ERR(new_dentry);
- if (IS_ERR(new_dentry))
- goto out;
-
- error = -EXDEV;
- if (old_path.mnt != new_path.mnt)
- goto out_dput;
- mnt_userns = mnt_user_ns(new_path.mnt);
- error = may_linkat(mnt_userns, &old_path);
- if (unlikely(error))
- goto out_dput;
- error = security_path_link(old_path.dentry, &new_path, new_dentry);
- if (error)
- goto out_dput;
- error = vfs_link(old_path.dentry, mnt_userns, new_path.dentry->d_inode,
- new_dentry, NULL);
-out_dput:
- done_path_create(&new_path, new_dentry);
-out:
- path_put(&old_path);
- return error;
-}
-
-int __init init_symlink(const char *oldname, const char *newname)
-{
- struct dentry *dentry;
- struct path path;
- int error;
-
- dentry = kern_path_create(AT_FDCWD, newname, &path, 0);
- if (IS_ERR(dentry))
- return PTR_ERR(dentry);
- error = security_path_symlink(&path, dentry, oldname);
- if (!error)
- error = vfs_symlink(mnt_user_ns(path.mnt), path.dentry->d_inode,
- dentry, oldname);
- done_path_create(&path, dentry);
- return error;
-}
-
int __init init_unlink(const char *pathname)
{
return do_unlinkat(AT_FDCWD, getname_kernel(pathname));
@@ -239,24 +156,6 @@ int __init init_mkdir(const char *pathname, umode_t mode)
return error;
}
-int __init init_rmdir(const char *pathname)
-{
- return do_rmdir(AT_FDCWD, getname_kernel(pathname));
-}
-
-int __init init_utimes(char *filename, struct timespec64 *ts)
-{
- struct path path;
- int error;
-
- error = kern_path(filename, 0, &path);
- if (error)
- return error;
- error = vfs_utimes(&path, ts);
- path_put(&path);
- return error;
-}
-
int __init init_dup(struct file *file)
{
int fd;
diff --git a/fs/internal.h b/fs/internal.h
index 87e96b9024ce..c57d5f0aa731 100644
--- a/fs/internal.h
+++ b/fs/internal.h
@@ -60,9 +60,6 @@ extern int filename_lookup(int dfd, struct filename *name, unsigned flags,
struct path *path, struct path *root);
extern int vfs_path_lookup(struct dentry *, struct vfsmount *,
const char *, unsigned int, struct path *);
-int do_rmdir(int dfd, struct filename *name);
-int do_unlinkat(int dfd, struct filename *name);
-int may_linkat(struct user_namespace *mnt_userns, struct path *link);
int do_renameat2(int olddfd, struct filename *oldname, int newdfd,
struct filename *newname, unsigned int flags);
int do_mkdirat(int dfd, struct filename *name, umode_t mode);
@@ -132,7 +129,6 @@ long do_sys_ftruncate(unsigned int fd, loff_t length, int small);
int chmod_common(const struct path *path, umode_t mode);
int do_fchownat(int dfd, const char __user *filename, uid_t user, gid_t group,
int flag);
-int chown_common(const struct path *path, uid_t user, gid_t group);
extern int vfs_open(const struct path *, struct file *);
/*
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 9ad5e3520fae..1cb51a54799b 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -2636,11 +2636,15 @@ static inline struct file *file_clone_open(struct file *file)
return dentry_open(&file->f_path, file->f_flags, file->f_cred);
}
extern int filp_close(struct file *, fl_owner_t id);
+extern int chown_common(const struct path *path, uid_t user, gid_t group);
+extern int do_rmdir(int dfd, struct filename *name);
+extern int do_unlinkat(int dfd, struct filename *name);
extern struct filename *getname_flags(const char __user *, int, int *);
extern struct filename *getname_uflags(const char __user *, int);
extern struct filename *getname(const char __user *);
extern struct filename *getname_kernel(const char *);
+extern int may_linkat(struct user_namespace *mnt_userns, struct path *link);
extern void putname(struct filename *name);
extern int finish_open(struct file *file, struct dentry *dentry,
diff --git a/include/linux/init_syscalls.h b/include/linux/init_syscalls.h
index 92045d18cbfc..196030cd958d 100644
--- a/include/linux/init_syscalls.h
+++ b/include/linux/init_syscalls.h
@@ -5,15 +5,9 @@ int __init init_mount(const char *dev_name, const char *dir_name,
int __init init_umount(const char *name, int flags);
int __init init_chdir(const char *filename);
int __init init_chroot(const char *filename);
-int __init init_chown(const char *filename, uid_t user, gid_t group, int flags);
-int __init init_chmod(const char *filename, umode_t mode);
int __init init_eaccess(const char *filename);
int __init init_stat(const char *filename, struct kstat *stat, int flags);
int __init init_mknod(const char *filename, umode_t mode, unsigned int dev);
-int __init init_link(const char *oldname, const char *newname);
-int __init init_symlink(const char *oldname, const char *newname);
int __init init_unlink(const char *pathname);
int __init init_mkdir(const char *pathname, umode_t mode);
-int __init init_rmdir(const char *pathname);
-int __init init_utimes(char *filename, struct timespec64 *ts);
int __init init_dup(struct file *file);
diff --git a/lib/cpio.c b/lib/cpio.c
index 5d150939704f..9a0120c638db 100644
--- a/lib/cpio.c
+++ b/lib/cpio.c
@@ -3,8 +3,9 @@
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/init.h>
-#include <linux/init_syscalls.h>
#include <linux/list.h>
+#include <linux/namei.h>
+#include <linux/security.h>
#include <linux/slab.h>
static ssize_t __init xwrite(struct cpio_context *ctx, struct file *file,
@@ -92,18 +93,25 @@ static void __init free_hash(struct cpio_context *ctx)
}
#ifdef CONFIG_INITRAMFS_PRESERVE_MTIME
-static void __init do_utime(char *filename, time64_t mtime)
+static void __init do_utime_path(const struct path *path, time64_t mtime)
{
struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
- init_utimes(filename, t);
+ vfs_utimes(path, t);
}
-static void __init do_utime_path(const struct path *path, time64_t mtime)
+static int __init do_utime(char *filename, time64_t mtime)
{
- struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
+ struct path path;
+ int error;
- vfs_utimes(path, t);
+ error = kern_path(filename, 0, &path);
+ if (error)
+ return error;
+ do_utime_path(&path, mtime);
+ path_put(&path);
+
+ return error;
}
static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t mtime)
@@ -133,12 +141,31 @@ static void __init dir_utime(struct cpio_context *ctx)
}
}
#else
-static void __init do_utime(char *filename, time64_t mtime) {}
+static int __init do_utime(char *filename, time64_t mtime) { return 0; }
static void __init do_utime_path(const struct path *path, time64_t mtime) {}
static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t mtime) { return 0; }
static void __init dir_utime(struct cpio_context *ctx) {}
#endif
+static int __init cpio_chown(const char *filename, uid_t user, gid_t group,
+ int flags)
+{
+ int lookup_flags = (flags & AT_SYMLINK_NOFOLLOW) ? 0 : LOOKUP_FOLLOW;
+ struct path path;
+ int error;
+
+ error = kern_path(filename, lookup_flags, &path);
+ if (error)
+ return error;
+ error = mnt_want_write(path.mnt);
+ if (!error) {
+ error = chown_common(&path, user, group);
+ mnt_drop_write(path.mnt);
+ }
+ path_put(&path);
+ return error;
+}
+
/* cpio header parsing */
static void __init parse_header(struct cpio_context *ctx, char *s)
@@ -269,27 +296,67 @@ static int __init do_reset(struct cpio_context *ctx)
return 1;
}
-static void __init clean_path(char *path, umode_t fmode)
+static void __init clean_path(char *pathname, umode_t fmode)
{
+ struct path path;
struct kstat st;
+ int error;
- if (!init_stat(path, &st, AT_SYMLINK_NOFOLLOW) &&
- (st.mode ^ fmode) & S_IFMT) {
+ error = kern_path(pathname, 0, &path);
+ if (error)
+ return;
+ error = vfs_getattr(&path, &st, STATX_BASIC_STATS, AT_NO_AUTOMOUNT);
+ path_put(&path);
+ if (error)
+ return;
+
+ if ((st.mode ^ fmode) & S_IFMT) {
if (S_ISDIR(st.mode))
- init_rmdir(path);
+ do_rmdir(AT_FDCWD, getname_kernel(pathname));
else
- init_unlink(path);
+ do_unlinkat(AT_FDCWD, getname_kernel(pathname));
}
}
static int __init maybe_link(struct cpio_context *ctx)
{
+ struct dentry *new_dentry;
+ struct path old_path, new_path;
+ struct user_namespace *mnt_userns;
+ int error;
+
if (ctx->nlink >= 2) {
char *old = find_link(ctx, ctx->major, ctx->minor, ctx->ino,
ctx->mode, ctx->collected);
if (old) {
clean_path(ctx->collected, 0);
- return (init_link(old, ctx->collected) < 0) ? -1 : 1;
+
+ error = kern_path(old, 0, &old_path);
+ if (error)
+ return error;
+
+ new_dentry = kern_path_create(AT_FDCWD, ctx->collected, &new_path, 0);
+ error = PTR_ERR(new_dentry);
+ if (IS_ERR(new_dentry))
+ goto out;
+
+ error = -EXDEV;
+ if (old_path.mnt != new_path.mnt)
+ goto out_dput;
+ mnt_userns = mnt_user_ns(new_path.mnt);
+ error = may_linkat(mnt_userns, &old_path);
+ if (unlikely(error))
+ goto out_dput;
+ error = security_path_link(old_path.dentry, &new_path, new_dentry);
+ if (error)
+ goto out_dput;
+ error = vfs_link(old_path.dentry, mnt_userns, new_path.dentry->d_inode,
+ new_dentry, NULL);
+out_dput:
+ done_path_create(&new_path, new_dentry);
+out:
+ path_put(&old_path);
+ return (error < 0) ? error : 1;
}
}
return 0;
@@ -297,6 +364,10 @@ static int __init maybe_link(struct cpio_context *ctx)
static int __init do_name(struct cpio_context *ctx)
{
+ struct dentry *dentry;
+ struct path path;
+ int error;
+
ctx->state = CPIO_SKIPIT;
ctx->next_state = CPIO_RESET;
if (strcmp(ctx->collected, "TRAILER!!!") == 0) {
@@ -325,16 +396,48 @@ static int __init do_name(struct cpio_context *ctx)
ctx->state = CPIO_COPYFILE;
}
} else if (S_ISDIR(ctx->mode)) {
- init_mkdir(ctx->collected, ctx->mode);
- init_chown(ctx->collected, ctx->uid, ctx->gid, 0);
- init_chmod(ctx->collected, ctx->mode);
+ dentry = kern_path_create(AT_FDCWD, ctx->collected, &path, LOOKUP_DIRECTORY);
+
+ if (!IS_ERR(dentry)) {
+ error = security_path_mkdir(&path, dentry, ctx->mode);
+ if (!error)
+ error = vfs_mkdir(mnt_user_ns(path.mnt),
+ path.dentry->d_inode,
+ dentry, ctx->mode);
+ done_path_create(&path, dentry);
+ } else {
+ error = PTR_ERR(dentry);
+ }
+
+ if (error && error != -EEXIST)
+ return error;
+
+ cpio_chown(ctx->collected, ctx->uid, ctx->gid, 0);
dir_add(ctx, ctx->collected, ctx->mtime);
} else if (S_ISBLK(ctx->mode) || S_ISCHR(ctx->mode) ||
S_ISFIFO(ctx->mode) || S_ISSOCK(ctx->mode)) {
if (maybe_link(ctx) == 0) {
- init_mknod(ctx->collected, ctx->mode, ctx->rdev);
- init_chown(ctx->collected, ctx->uid, ctx->gid, 0);
- init_chmod(ctx->collected, ctx->mode);
+ if (S_ISFIFO(ctx->mode) || S_ISSOCK(ctx->mode))
+ ctx->rdev = 0;
+
+ dentry = kern_path_create(AT_FDCWD, ctx->collected, &path, 0);
+ if (!IS_ERR(dentry)) {
+ error = security_path_mknod(&path, dentry, ctx->mode,
+ ctx->rdev);
+ if (!error)
+ error = vfs_mknod(mnt_user_ns(path.mnt),
+ path.dentry->d_inode,
+ dentry, ctx->mode,
+ new_decode_dev(ctx->rdev));
+ done_path_create(&path, dentry);
+ } else {
+ error = PTR_ERR(dentry);
+ }
+
+ if (error && error != -EEXIST)
+ return error;
+
+ cpio_chown(ctx->collected, ctx->uid, ctx->gid, 0);
do_utime(ctx->collected, ctx->mtime);
}
}
@@ -373,10 +476,27 @@ static int __init do_copy(struct cpio_context *ctx)
static int __init do_symlink(struct cpio_context *ctx)
{
+ struct dentry *dentry;
+ struct path path;
+ int error;
+
ctx->collected[N_ALIGN(ctx->name_len) + ctx->body_len] = '\0';
clean_path(ctx->collected, 0);
- init_symlink(ctx->collected + N_ALIGN(ctx->name_len), ctx->collected);
- init_chown(ctx->collected, ctx->uid, ctx->gid, AT_SYMLINK_NOFOLLOW);
+
+ dentry = kern_path_create(AT_FDCWD, ctx->collected, &path, 0);
+ if (IS_ERR(dentry))
+ return PTR_ERR(dentry);
+ error = security_path_symlink(&path, dentry,
+ ctx->collected + N_ALIGN(ctx->name_len));
+ if (!error)
+ error = vfs_symlink(mnt_user_ns(path.mnt), path.dentry->d_inode,
+ dentry,
+ ctx->collected + N_ALIGN(ctx->name_len));
+ done_path_create(&path, dentry);
+ if (error)
+ return error;
+
+ cpio_chown(ctx->collected, ctx->uid, ctx->gid, AT_SYMLINK_NOFOLLOW);
do_utime(ctx->collected, ctx->mtime);
ctx->state = CPIO_SKIPIT;
ctx->next_state = CPIO_RESET;
--
2.30.2
The cpio handling routines can be useful outside of just initialising
the initramfs. Pull the functions into lib/ and all of the static state
into a context structure in preparation for enabling the use of this
functionality outside of __init code.
Signed-off-by: Jonathan McDowell <[email protected]>
---
v2:
- Fix printf format string in populate_initrd_image (kernel test robot, i386 build)
- Include <linux/limits.h> in cpio.h (kernel test robot, uml build)
---
include/linux/cpio.h | 79 +++++++
init/initramfs.c | 518 ++++---------------------------------------
lib/Makefile | 2 +-
lib/cpio.c | 454 +++++++++++++++++++++++++++++++++++++
4 files changed, 579 insertions(+), 474 deletions(-)
create mode 100644 include/linux/cpio.h
create mode 100644 lib/cpio.c
diff --git a/include/linux/cpio.h b/include/linux/cpio.h
new file mode 100644
index 000000000000..2f9fd735331e
--- /dev/null
+++ b/include/linux/cpio.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_CPIO_H
+#define _LINUX_CPIO_H
+
+#include <linux/init.h>
+#include <linux/limits.h>
+#include <linux/time.h>
+#include <linux/types.h>
+
+/* Must be a power of 2 */
+#define CPIO_LINK_HASH_SIZE 32
+
+#define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
+
+enum cpio_state {
+ CPIO_START,
+ CPIO_COLLECT,
+ CPIO_GOTHEADER,
+ CPIO_SKIPIT,
+ CPIO_GOTNAME,
+ CPIO_COPYFILE,
+ CPIO_GOTSYMLINK,
+ CPIO_RESET
+};
+
+struct cpio_dir_entry {
+ struct list_head list;
+ time64_t mtime;
+ char name[];
+};
+
+struct cpio_link_hash {
+ int ino, minor, major;
+ umode_t mode;
+ struct cpio_link_hash *next;
+ char name[N_ALIGN(PATH_MAX)];
+};
+
+struct cpio_context {
+ enum cpio_state state, next_state;
+ char *header_buf, *symlink_buf, *name_buf;
+ loff_t this_header, next_header;
+ char *victim;
+ unsigned long byte_count;
+ bool csum_present;
+ u32 io_csum;
+ char *errmsg;
+
+ char *collected;
+ long remains;
+ char *collect;
+
+ struct file *wfile;
+ loff_t wfile_pos;
+
+ /* Header fields */
+ unsigned long ino, major, minor, nlink;
+ umode_t mode;
+ unsigned long body_len, name_len;
+ uid_t uid;
+ gid_t gid;
+ unsigned int rdev;
+ u32 hdr_csum;
+ time64_t mtime;
+
+ /* Link hash */
+ struct cpio_link_hash *link_hash[CPIO_LINK_HASH_SIZE];
+
+ struct list_head dir_list;
+};
+
+int __init cpio_start(struct cpio_context *ctx);
+void __init cpio_finish(struct cpio_context *ctx);
+long __init cpio_write_buffer(struct cpio_context *ctx, char *buf,
+ unsigned long len);
+long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
+ unsigned long len);
+
+#endif /* _LINUX_CPIO_H */
diff --git a/init/initramfs.c b/init/initramfs.c
index 18229cfe8906..00c101d04f4b 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/init.h>
#include <linux/async.h>
-#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/fcntl.h>
@@ -14,43 +13,9 @@
#include <linux/memblock.h>
#include <linux/mm.h>
#include <linux/namei.h>
-#include <linux/init_syscalls.h>
#include <linux/task_work.h>
#include <linux/umh.h>
-
-static __initdata bool csum_present;
-static __initdata u32 io_csum;
-
-static ssize_t __init xwrite(struct file *file, const unsigned char *p,
- size_t count, loff_t *pos)
-{
- ssize_t out = 0;
-
- /* sys_write only can write MAX_RW_COUNT aka 2G-4K bytes at most */
- while (count) {
- ssize_t rv = kernel_write(file, p, count, pos);
-
- if (rv < 0) {
- if (rv == -EINTR || rv == -EAGAIN)
- continue;
- return out ? out : rv;
- } else if (rv == 0)
- break;
-
- if (csum_present) {
- ssize_t i;
-
- for (i = 0; i < rv; i++)
- io_csum += p[i];
- }
-
- p += rv;
- out += rv;
- count -= rv;
- }
-
- return out;
-}
+#include <linux/cpio.h>
static __initdata char *message;
static void __init error(char *x)
@@ -69,423 +34,17 @@ static void panic_show_mem(const char *fmt, ...)
va_end(args);
}
-/* link hash */
-
-#define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
-
-static __initdata struct hash {
- int ino, minor, major;
- umode_t mode;
- struct hash *next;
- char name[N_ALIGN(PATH_MAX)];
-} *head[32];
-
-static inline int hash(int major, int minor, int ino)
-{
- unsigned long tmp = ino + minor + (major << 3);
- tmp += tmp >> 5;
- return tmp & 31;
-}
-
-static char __init *find_link(int major, int minor, int ino,
- umode_t mode, char *name)
-{
- struct hash **p, *q;
- for (p = head + hash(major, minor, ino); *p; p = &(*p)->next) {
- if ((*p)->ino != ino)
- continue;
- if ((*p)->minor != minor)
- continue;
- if ((*p)->major != major)
- continue;
- if (((*p)->mode ^ mode) & S_IFMT)
- continue;
- return (*p)->name;
- }
- q = kmalloc(sizeof(struct hash), GFP_KERNEL);
- if (!q)
- panic_show_mem("can't allocate link hash entry");
- q->major = major;
- q->minor = minor;
- q->ino = ino;
- q->mode = mode;
- strcpy(q->name, name);
- q->next = NULL;
- *p = q;
- return NULL;
-}
-
-static void __init free_hash(void)
-{
- struct hash **p, *q;
- for (p = head; p < head + 32; p++) {
- while (*p) {
- q = *p;
- *p = q->next;
- kfree(q);
- }
- }
-}
-
-#ifdef CONFIG_INITRAMFS_PRESERVE_MTIME
-static void __init do_utime(char *filename, time64_t mtime)
-{
- struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
- init_utimes(filename, t);
-}
-
-static void __init do_utime_path(const struct path *path, time64_t mtime)
-{
- struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
- vfs_utimes(path, t);
-}
-
-static __initdata LIST_HEAD(dir_list);
-struct dir_entry {
- struct list_head list;
- time64_t mtime;
- char name[];
-};
-
-static void __init dir_add(const char *name, time64_t mtime)
-{
- size_t nlen = strlen(name) + 1;
- struct dir_entry *de;
-
- de = kmalloc(sizeof(struct dir_entry) + nlen, GFP_KERNEL);
- if (!de)
- panic_show_mem("can't allocate dir_entry buffer");
- INIT_LIST_HEAD(&de->list);
- strscpy(de->name, name, nlen);
- de->mtime = mtime;
- list_add(&de->list, &dir_list);
-}
-
-static void __init dir_utime(void)
-{
- struct dir_entry *de, *tmp;
- list_for_each_entry_safe(de, tmp, &dir_list, list) {
- list_del(&de->list);
- do_utime(de->name, de->mtime);
- kfree(de);
- }
-}
-#else
-static void __init do_utime(char *filename, time64_t mtime) {}
-static void __init do_utime_path(const struct path *path, time64_t mtime) {}
-static void __init dir_add(const char *name, time64_t mtime) {}
-static void __init dir_utime(void) {}
-#endif
-
-static __initdata time64_t mtime;
-
-/* cpio header parsing */
-
-static __initdata unsigned long ino, major, minor, nlink;
-static __initdata umode_t mode;
-static __initdata unsigned long body_len, name_len;
-static __initdata uid_t uid;
-static __initdata gid_t gid;
-static __initdata unsigned rdev;
-static __initdata u32 hdr_csum;
-
-static void __init parse_header(char *s)
-{
- unsigned long parsed[13];
- char buf[9];
- int i;
-
- buf[8] = '\0';
- for (i = 0, s += 6; i < 13; i++, s += 8) {
- memcpy(buf, s, 8);
- parsed[i] = simple_strtoul(buf, NULL, 16);
- }
- ino = parsed[0];
- mode = parsed[1];
- uid = parsed[2];
- gid = parsed[3];
- nlink = parsed[4];
- mtime = parsed[5]; /* breaks in y2106 */
- body_len = parsed[6];
- major = parsed[7];
- minor = parsed[8];
- rdev = new_encode_dev(MKDEV(parsed[9], parsed[10]));
- name_len = parsed[11];
- hdr_csum = parsed[12];
-}
-
-/* FSM */
-
-static __initdata enum state {
- Start,
- Collect,
- GotHeader,
- SkipIt,
- GotName,
- CopyFile,
- GotSymlink,
- Reset
-} state, next_state;
-
-static __initdata char *victim;
-static unsigned long byte_count __initdata;
-static __initdata loff_t this_header, next_header;
-
-static inline void __init eat(unsigned n)
-{
- victim += n;
- this_header += n;
- byte_count -= n;
-}
-
-static __initdata char *collected;
-static long remains __initdata;
-static __initdata char *collect;
-
-static void __init read_into(char *buf, unsigned size, enum state next)
-{
- if (byte_count >= size) {
- collected = victim;
- eat(size);
- state = next;
- } else {
- collect = collected = buf;
- remains = size;
- next_state = next;
- state = Collect;
- }
-}
-
-static __initdata char *header_buf, *symlink_buf, *name_buf;
-
-static int __init do_start(void)
-{
- read_into(header_buf, 110, GotHeader);
- return 0;
-}
-
-static int __init do_collect(void)
-{
- unsigned long n = remains;
- if (byte_count < n)
- n = byte_count;
- memcpy(collect, victim, n);
- eat(n);
- collect += n;
- if ((remains -= n) != 0)
- return 1;
- state = next_state;
- return 0;
-}
-
-static int __init do_header(void)
-{
- if (!memcmp(collected, "070701", 6)) {
- csum_present = false;
- } else if (!memcmp(collected, "070702", 6)) {
- csum_present = true;
- } else {
- if (memcmp(collected, "070707", 6) == 0)
- error("incorrect cpio method used: use -H newc option");
- else
- error("no cpio magic");
- return 1;
- }
- parse_header(collected);
- next_header = this_header + N_ALIGN(name_len) + body_len;
- next_header = (next_header + 3) & ~3;
- state = SkipIt;
- if (name_len <= 0 || name_len > PATH_MAX)
- return 0;
- if (S_ISLNK(mode)) {
- if (body_len > PATH_MAX)
- return 0;
- collect = collected = symlink_buf;
- remains = N_ALIGN(name_len) + body_len;
- next_state = GotSymlink;
- state = Collect;
- return 0;
- }
- if (S_ISREG(mode) || !body_len)
- read_into(name_buf, N_ALIGN(name_len), GotName);
- return 0;
-}
-
-static int __init do_skip(void)
-{
- if (this_header + byte_count < next_header) {
- eat(byte_count);
- return 1;
- } else {
- eat(next_header - this_header);
- state = next_state;
- return 0;
- }
-}
-
-static int __init do_reset(void)
-{
- while (byte_count && *victim == '\0')
- eat(1);
- if (byte_count && (this_header & 3))
- error("broken padding");
- return 1;
-}
-
-static void __init clean_path(char *path, umode_t fmode)
-{
- struct kstat st;
-
- if (!init_stat(path, &st, AT_SYMLINK_NOFOLLOW) &&
- (st.mode ^ fmode) & S_IFMT) {
- if (S_ISDIR(st.mode))
- init_rmdir(path);
- else
- init_unlink(path);
- }
-}
-
-static int __init maybe_link(void)
-{
- if (nlink >= 2) {
- char *old = find_link(major, minor, ino, mode, collected);
- if (old) {
- clean_path(collected, 0);
- return (init_link(old, collected) < 0) ? -1 : 1;
- }
- }
- return 0;
-}
-
-static __initdata struct file *wfile;
-static __initdata loff_t wfile_pos;
-
-static int __init do_name(void)
-{
- state = SkipIt;
- next_state = Reset;
- if (strcmp(collected, "TRAILER!!!") == 0) {
- free_hash();
- return 0;
- }
- clean_path(collected, mode);
- if (S_ISREG(mode)) {
- int ml = maybe_link();
- if (ml >= 0) {
- int openflags = O_WRONLY|O_CREAT;
- if (ml != 1)
- openflags |= O_TRUNC;
- wfile = filp_open(collected, openflags, mode);
- if (IS_ERR(wfile))
- return 0;
- wfile_pos = 0;
- io_csum = 0;
-
- vfs_fchown(wfile, uid, gid);
- vfs_fchmod(wfile, mode);
- if (body_len)
- vfs_truncate(&wfile->f_path, body_len);
- state = CopyFile;
- }
- } else if (S_ISDIR(mode)) {
- init_mkdir(collected, mode);
- init_chown(collected, uid, gid, 0);
- init_chmod(collected, mode);
- dir_add(collected, mtime);
- } else if (S_ISBLK(mode) || S_ISCHR(mode) ||
- S_ISFIFO(mode) || S_ISSOCK(mode)) {
- if (maybe_link() == 0) {
- init_mknod(collected, mode, rdev);
- init_chown(collected, uid, gid, 0);
- init_chmod(collected, mode);
- do_utime(collected, mtime);
- }
- }
- return 0;
-}
+static unsigned long my_inptr; /* index of next byte to be processed in inbuf */
-static int __init do_copy(void)
-{
- if (byte_count >= body_len) {
- if (xwrite(wfile, victim, body_len, &wfile_pos) != body_len)
- error("write error");
-
- do_utime_path(&wfile->f_path, mtime);
- fput(wfile);
- if (csum_present && io_csum != hdr_csum)
- error("bad data checksum");
- eat(body_len);
- state = SkipIt;
- return 0;
- } else {
- if (xwrite(wfile, victim, byte_count, &wfile_pos) != byte_count)
- error("write error");
- body_len -= byte_count;
- eat(byte_count);
- return 1;
- }
-}
+#include <linux/decompress/generic.h>
-static int __init do_symlink(void)
-{
- collected[N_ALIGN(name_len) + body_len] = '\0';
- clean_path(collected, 0);
- init_symlink(collected + N_ALIGN(name_len), collected);
- init_chown(collected, uid, gid, AT_SYMLINK_NOFOLLOW);
- do_utime(collected, mtime);
- state = SkipIt;
- next_state = Reset;
- return 0;
-}
+static struct cpio_context ctx __initdata;
-static __initdata int (*actions[])(void) = {
- [Start] = do_start,
- [Collect] = do_collect,
- [GotHeader] = do_header,
- [SkipIt] = do_skip,
- [GotName] = do_name,
- [CopyFile] = do_copy,
- [GotSymlink] = do_symlink,
- [Reset] = do_reset,
-};
-
-static long __init write_buffer(char *buf, unsigned long len)
+static long __init process_buffer(void *bufv, unsigned long len)
{
- byte_count = len;
- victim = buf;
-
- while (!actions[state]())
- ;
- return len - byte_count;
+ return cpio_process_buffer(&ctx, bufv, len);
}
-static long __init flush_buffer(void *bufv, unsigned long len)
-{
- char *buf = (char *) bufv;
- long written;
- long origLen = len;
- if (message)
- return -1;
- while ((written = write_buffer(buf, len)) < len && !message) {
- char c = buf[written];
- if (c == '0') {
- buf += written;
- len -= written;
- state = Start;
- } else if (c == 0) {
- buf += written;
- len -= written;
- state = Reset;
- } else
- error("junk within compressed archive");
- }
- return origLen;
-}
-
-static unsigned long my_inptr; /* index of next byte to be processed in inbuf */
-
-#include <linux/decompress/generic.h>
-
static char * __init unpack_to_rootfs(char *buf, unsigned long len)
{
long written;
@@ -493,36 +52,34 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
const char *compress_name;
static __initdata char msg_buf[64];
- header_buf = kmalloc(110, GFP_KERNEL);
- symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
- name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
-
- if (!header_buf || !symlink_buf || !name_buf)
+ if (cpio_start(&ctx))
panic_show_mem("can't allocate buffers");
- state = Start;
- this_header = 0;
message = NULL;
while (!message && len) {
- loff_t saved_offset = this_header;
- if (*buf == '0' && !(this_header & 3)) {
- state = Start;
- written = write_buffer(buf, len);
+ loff_t saved_offset = ctx.this_header;
+
+ if (*buf == '0' && !(ctx.this_header & 3)) {
+ ctx.state = CPIO_START;
+ written = cpio_write_buffer(&ctx, buf, len);
buf += written;
len -= written;
+ if (ctx.errmsg)
+ message = ctx.errmsg;
continue;
}
if (!*buf) {
buf++;
len--;
- this_header++;
+ ctx.this_header++;
continue;
}
- this_header = 0;
+ ctx.this_header = 0;
decompress = decompress_method(buf, len, &compress_name);
pr_debug("Detected %s compressed data\n", compress_name);
if (decompress) {
- int res = decompress(buf, len, NULL, flush_buffer, NULL,
+ int res = decompress(buf, len, NULL,
+ process_buffer, NULL,
&my_inptr, error);
if (res)
error("decompressor failed");
@@ -535,16 +92,13 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
}
} else
error("invalid magic at start of compressed archive");
- if (state != Reset)
+ if (ctx.state != CPIO_RESET)
error("junk at the end of compressed archive");
- this_header = saved_offset + my_inptr;
+ ctx.this_header = saved_offset + my_inptr;
buf += my_inptr;
len -= my_inptr;
}
- dir_utime();
- kfree(name_buf);
- kfree(symlink_buf);
- kfree(header_buf);
+ cpio_finish(&ctx);
return message;
}
@@ -672,9 +226,11 @@ static inline bool kexec_free_initrd(void)
#ifdef CONFIG_BLK_DEV_RAM
static void __init populate_initrd_image(char *err)
{
- ssize_t written;
struct file *file;
+ unsigned char *p;
+ ssize_t written;
loff_t pos = 0;
+ size_t count;
unpack_to_rootfs(__initramfs_start, __initramfs_size);
@@ -684,11 +240,27 @@ static void __init populate_initrd_image(char *err)
if (IS_ERR(file))
return;
- written = xwrite(file, (char *)initrd_start, initrd_end - initrd_start,
- &pos);
- if (written != initrd_end - initrd_start)
- pr_err("/initrd.image: incomplete write (%zd != %ld)\n",
- written, initrd_end - initrd_start);
+ count = initrd_end - initrd_start;
+ p = (char *)initrd_start;
+ while (count) {
+ written = kernel_write(file, p, count, &pos);
+
+ if (written < 0) {
+ if (written == -EINTR || written == -EAGAIN)
+ continue;
+ break;
+ } else if (written == 0) {
+ break;
+ }
+
+ p += written;
+ count -= written;
+ }
+
+ if (count != 0)
+ pr_err("/initrd.image: incomplete write (%ld != %ld)\n",
+ (initrd_end - initrd_start) - count,
+ initrd_end - initrd_start);
fput(file);
}
#endif /* CONFIG_BLK_DEV_RAM */
diff --git a/lib/Makefile b/lib/Makefile
index f99bf61f8bbc..8db946deb71c 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -34,7 +34,7 @@ lib-y := ctype.o string.o vsprintf.o cmdline.o \
is_single_threaded.o plist.o decompress.o kobject_uevent.o \
earlycpio.o seq_buf.o siphash.o dec_and_lock.o \
nmi_backtrace.o nodemask.o win_minmax.o memcat_p.o \
- buildid.o
+ buildid.o cpio.o
lib-$(CONFIG_PRINTK) += dump_stack.o
lib-$(CONFIG_SMP) += cpumask.o
diff --git a/lib/cpio.c b/lib/cpio.c
new file mode 100644
index 000000000000..c71bebd4cc98
--- /dev/null
+++ b/lib/cpio.c
@@ -0,0 +1,454 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/cpio.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/init_syscalls.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+
+static ssize_t __init xwrite(struct cpio_context *ctx, struct file *file,
+ const unsigned char *p, size_t count, loff_t *pos)
+{
+ ssize_t out = 0;
+
+ /* sys_write only can write MAX_RW_COUNT aka 2G-4K bytes at most */
+ while (count) {
+ ssize_t rv = kernel_write(file, p, count, pos);
+
+ if (rv < 0) {
+ if (rv == -EINTR || rv == -EAGAIN)
+ continue;
+ return out ? out : rv;
+ } else if (rv == 0) {
+ break;
+ }
+
+ if (ctx->csum_present) {
+ ssize_t i;
+
+ for (i = 0; i < rv; i++)
+ ctx->io_csum += p[i];
+ }
+
+ p += rv;
+ out += rv;
+ count -= rv;
+ }
+
+ return out;
+}
+
+/* link hash */
+
+static inline int hash(int major, int minor, int ino)
+{
+ unsigned long tmp = ino + minor + (major << 3);
+
+ tmp += tmp >> 5;
+ return tmp & (CPIO_LINK_HASH_SIZE - 1);
+}
+
+static char __init *find_link(struct cpio_context *ctx, int major, int minor,
+ int ino, umode_t mode, char *name)
+{
+ struct cpio_link_hash **p, *q;
+
+ for (p = ctx->link_hash + hash(major, minor, ino); *p; p = &(*p)->next) {
+ if ((*p)->ino != ino)
+ continue;
+ if ((*p)->minor != minor)
+ continue;
+ if ((*p)->major != major)
+ continue;
+ if (((*p)->mode ^ mode) & S_IFMT)
+ continue;
+ return (*p)->name;
+ }
+ q = kmalloc(sizeof(*q), GFP_KERNEL);
+ if (!q)
+ return ERR_PTR(-ENOMEM);
+ q->major = major;
+ q->minor = minor;
+ q->ino = ino;
+ q->mode = mode;
+ strcpy(q->name, name);
+ q->next = NULL;
+ *p = q;
+ return NULL;
+}
+
+static void __init free_hash(struct cpio_context *ctx)
+{
+ struct cpio_link_hash **p, *q;
+
+ for (p = ctx->link_hash; p < ctx->link_hash + CPIO_LINK_HASH_SIZE; p++) {
+ while (*p) {
+ q = *p;
+ *p = q->next;
+ kfree(q);
+ }
+ }
+}
+
+#ifdef CONFIG_INITRAMFS_PRESERVE_MTIME
+static void __init do_utime(char *filename, time64_t mtime)
+{
+ struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
+
+ init_utimes(filename, t);
+}
+
+static void __init do_utime_path(const struct path *path, time64_t mtime)
+{
+ struct timespec64 t[2] = { { .tv_sec = mtime }, { .tv_sec = mtime } };
+
+ vfs_utimes(path, t);
+}
+
+static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t mtime)
+{
+ size_t nlen = strlen(name) + 1;
+ struct cpio_dir_entry *de;
+
+ de = kmalloc(sizeof(*de) + nlen, GFP_KERNEL);
+ if (!de)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&de->list);
+ strscpy(de->name, name, nlen);
+ de->mtime = mtime;
+ list_add(&de->list, &ctx->dir_list);
+
+ return 0;
+}
+
+static void __init dir_utime(struct cpio_context *ctx)
+{
+ struct cpio_dir_entry *de, *tmp;
+
+ list_for_each_entry_safe(de, tmp, &ctx->dir_list, list) {
+ list_del(&de->list);
+ do_utime(de->name, de->mtime);
+ kfree(de);
+ }
+}
+#else
+static void __init do_utime(char *filename, time64_t mtime) {}
+static void __init do_utime_path(const struct path *path, time64_t mtime) {}
+static int __init dir_add(struct cpio_context *ctx, const char *name, time64_t mtime) { return 0; }
+static void __init dir_utime(struct cpio_context *ctx) {}
+#endif
+
+/* cpio header parsing */
+
+static void __init parse_header(struct cpio_context *ctx, char *s)
+{
+ unsigned long parsed[13];
+ char buf[9];
+ int i;
+
+ buf[8] = '\0';
+ for (i = 0, s += 6; i < 13; i++, s += 8) {
+ memcpy(buf, s, 8);
+ parsed[i] = simple_strtoul(buf, NULL, 16);
+ }
+ ctx->ino = parsed[0];
+ ctx->mode = parsed[1];
+ ctx->uid = parsed[2];
+ ctx->gid = parsed[3];
+ ctx->nlink = parsed[4];
+ ctx->mtime = parsed[5]; /* breaks in y2106 */
+ ctx->body_len = parsed[6];
+ ctx->major = parsed[7];
+ ctx->minor = parsed[8];
+ ctx->rdev = new_encode_dev(MKDEV(parsed[9], parsed[10]));
+ ctx->name_len = parsed[11];
+ ctx->hdr_csum = parsed[12];
+}
+
+/* FSM */
+
+static inline void __init eat(struct cpio_context *ctx, unsigned int n)
+{
+ ctx->victim += n;
+ ctx->this_header += n;
+ ctx->byte_count -= n;
+}
+
+static void __init read_into(struct cpio_context *ctx, char *buf,
+ unsigned int size, enum cpio_state next)
+{
+ if (ctx->byte_count >= size) {
+ ctx->collected = ctx->victim;
+ eat(ctx, size);
+ ctx->state = next;
+ } else {
+ ctx->collect = buf;
+ ctx->collected = buf;
+ ctx->remains = size;
+ ctx->next_state = next;
+ ctx->state = CPIO_COLLECT;
+ }
+}
+
+static int __init do_start(struct cpio_context *ctx)
+{
+ read_into(ctx, ctx->header_buf, 110, CPIO_GOTHEADER);
+ return 0;
+}
+
+static int __init do_collect(struct cpio_context *ctx)
+{
+ unsigned long n = ctx->remains;
+
+ if (ctx->byte_count < n)
+ n = ctx->byte_count;
+ memcpy(ctx->collect, ctx->victim, n);
+ eat(ctx, n);
+ ctx->collect += n;
+ ctx->remains -= n;
+
+ if (ctx->remains != 0)
+ return 1;
+
+ ctx->state = ctx->next_state;
+ return 0;
+}
+
+static int __init do_header(struct cpio_context *ctx)
+{
+ if (!memcmp(ctx->collected, "070701", 6)) {
+ ctx->csum_present = false;
+ } else if (!memcmp(ctx->collected, "070702", 6)) {
+ ctx->csum_present = true;
+ } else {
+ if (memcmp(ctx->collected, "070707", 6) == 0)
+ ctx->errmsg = "incorrect cpio method used: use -H newc option";
+ else
+ ctx->errmsg = "no cpio magic";
+ return 1;
+ }
+ parse_header(ctx, ctx->collected);
+ ctx->next_header = ctx->this_header + N_ALIGN(ctx->name_len) + ctx->body_len;
+ ctx->next_header = (ctx->next_header + 3) & ~3;
+ ctx->state = CPIO_SKIPIT;
+ if (ctx->name_len <= 0 || ctx->name_len > PATH_MAX)
+ return 0;
+ if (S_ISLNK(ctx->mode)) {
+ if (ctx->body_len > PATH_MAX)
+ return 0;
+ ctx->collect = ctx->symlink_buf;
+ ctx->collected = ctx->symlink_buf;
+ ctx->remains = N_ALIGN(ctx->name_len) + ctx->body_len;
+ ctx->next_state = CPIO_GOTSYMLINK;
+ ctx->state = CPIO_COLLECT;
+ return 0;
+ }
+ if (S_ISREG(ctx->mode) || !ctx->body_len)
+ read_into(ctx, ctx->name_buf, N_ALIGN(ctx->name_len), CPIO_GOTNAME);
+ return 0;
+}
+
+static int __init do_skip(struct cpio_context *ctx)
+{
+ if (ctx->this_header + ctx->byte_count < ctx->next_header) {
+ eat(ctx, ctx->byte_count);
+ return 1;
+ }
+
+ eat(ctx, ctx->next_header - ctx->this_header);
+ ctx->state = ctx->next_state;
+ return 0;
+}
+
+static int __init do_reset(struct cpio_context *ctx)
+{
+ while (ctx->byte_count && *ctx->victim == '\0')
+ eat(ctx, 1);
+ if (ctx->byte_count && (ctx->this_header & 3))
+ ctx->errmsg = "broken padding";
+ return 1;
+}
+
+static void __init clean_path(char *path, umode_t fmode)
+{
+ struct kstat st;
+
+ if (!init_stat(path, &st, AT_SYMLINK_NOFOLLOW) &&
+ (st.mode ^ fmode) & S_IFMT) {
+ if (S_ISDIR(st.mode))
+ init_rmdir(path);
+ else
+ init_unlink(path);
+ }
+}
+
+static int __init maybe_link(struct cpio_context *ctx)
+{
+ if (ctx->nlink >= 2) {
+ char *old = find_link(ctx, ctx->major, ctx->minor, ctx->ino,
+ ctx->mode, ctx->collected);
+ if (old) {
+ clean_path(ctx->collected, 0);
+ return (init_link(old, ctx->collected) < 0) ? -1 : 1;
+ }
+ }
+ return 0;
+}
+
+static int __init do_name(struct cpio_context *ctx)
+{
+ ctx->state = CPIO_SKIPIT;
+ ctx->next_state = CPIO_RESET;
+ if (strcmp(ctx->collected, "TRAILER!!!") == 0) {
+ free_hash(ctx);
+ return 0;
+ }
+ clean_path(ctx->collected, ctx->mode);
+ if (S_ISREG(ctx->mode)) {
+ int ml = maybe_link(ctx);
+
+ if (ml >= 0) {
+ int openflags = O_WRONLY | O_CREAT;
+
+ if (ml != 1)
+ openflags |= O_TRUNC;
+ ctx->wfile = filp_open(ctx->collected, openflags, ctx->mode);
+ if (IS_ERR(ctx->wfile))
+ return 0;
+ ctx->wfile_pos = 0;
+ ctx->io_csum = 0;
+
+ vfs_fchown(ctx->wfile, ctx->uid, ctx->gid);
+ vfs_fchmod(ctx->wfile, ctx->mode);
+ if (ctx->body_len)
+ vfs_truncate(&ctx->wfile->f_path, ctx->body_len);
+ ctx->state = CPIO_COPYFILE;
+ }
+ } else if (S_ISDIR(ctx->mode)) {
+ init_mkdir(ctx->collected, ctx->mode);
+ init_chown(ctx->collected, ctx->uid, ctx->gid, 0);
+ init_chmod(ctx->collected, ctx->mode);
+ dir_add(ctx, ctx->collected, ctx->mtime);
+ } else if (S_ISBLK(ctx->mode) || S_ISCHR(ctx->mode) ||
+ S_ISFIFO(ctx->mode) || S_ISSOCK(ctx->mode)) {
+ if (maybe_link(ctx) == 0) {
+ init_mknod(ctx->collected, ctx->mode, ctx->rdev);
+ init_chown(ctx->collected, ctx->uid, ctx->gid, 0);
+ init_chmod(ctx->collected, ctx->mode);
+ do_utime(ctx->collected, ctx->mtime);
+ }
+ }
+ return 0;
+}
+
+static int __init do_copy(struct cpio_context *ctx)
+{
+ if (ctx->byte_count >= ctx->body_len) {
+ if (xwrite(ctx, ctx->wfile, ctx->victim, ctx->body_len,
+ &ctx->wfile_pos) != ctx->body_len)
+ ctx->errmsg = "write error";
+
+ do_utime_path(&ctx->wfile->f_path, ctx->mtime);
+ fput(ctx->wfile);
+ if (ctx->csum_present && ctx->io_csum != ctx->hdr_csum)
+ ctx->errmsg = "bad data checksum";
+ eat(ctx, ctx->body_len);
+ ctx->state = CPIO_SKIPIT;
+ return 0;
+ }
+
+ if (xwrite(ctx, ctx->wfile, ctx->victim, ctx->byte_count,
+ &ctx->wfile_pos) != ctx->byte_count)
+ ctx->errmsg = "write error";
+ ctx->body_len -= ctx->byte_count;
+ eat(ctx, ctx->byte_count);
+ return 1;
+}
+
+static int __init do_symlink(struct cpio_context *ctx)
+{
+ ctx->collected[N_ALIGN(ctx->name_len) + ctx->body_len] = '\0';
+ clean_path(ctx->collected, 0);
+ init_symlink(ctx->collected + N_ALIGN(ctx->name_len), ctx->collected);
+ init_chown(ctx->collected, ctx->uid, ctx->gid, AT_SYMLINK_NOFOLLOW);
+ do_utime(ctx->collected, ctx->mtime);
+ ctx->state = CPIO_SKIPIT;
+ ctx->next_state = CPIO_RESET;
+ return 0;
+}
+
+static __initdata int (*actions[])(struct cpio_context *) = {
+ [CPIO_START] = do_start,
+ [CPIO_COLLECT] = do_collect,
+ [CPIO_GOTHEADER] = do_header,
+ [CPIO_SKIPIT] = do_skip,
+ [CPIO_GOTNAME] = do_name,
+ [CPIO_COPYFILE] = do_copy,
+ [CPIO_GOTSYMLINK] = do_symlink,
+ [CPIO_RESET] = do_reset,
+};
+
+long __init cpio_write_buffer(struct cpio_context *ctx, char *buf,
+ unsigned long len)
+{
+ ctx->byte_count = len;
+ ctx->victim = buf;
+
+ while (!actions[ctx->state](ctx))
+ ;
+ return len - ctx->byte_count;
+}
+
+long __init cpio_process_buffer(struct cpio_context *ctx, void *bufv,
+ unsigned long len)
+{
+ char *buf = (char *)bufv;
+ long written;
+ long left = len;
+
+ if (ctx->errmsg)
+ return -1;
+
+ while ((written = cpio_write_buffer(ctx, buf, left)) < left && !ctx->errmsg) {
+ char c = buf[written];
+
+ if (c == '0') {
+ buf += written;
+ left -= written;
+ ctx->state = CPIO_START;
+ } else if (c == 0) {
+ buf += written;
+ left -= written;
+ ctx->state = CPIO_RESET;
+ } else {
+ ctx->errmsg = "junk within compressed archive";
+ }
+ }
+
+ return len;
+}
+
+int __init cpio_start(struct cpio_context *ctx)
+{
+ ctx->header_buf = kmalloc(110, GFP_KERNEL);
+ ctx->symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
+ ctx->name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
+
+ if (!ctx->header_buf || !ctx->symlink_buf || !ctx->name_buf)
+ return -ENOMEM;
+
+ ctx->state = CPIO_START;
+ ctx->this_header = 0;
+ INIT_LIST_HEAD(&ctx->dir_list);
+
+ return 0;
+}
+
+void __init cpio_finish(struct cpio_context *ctx)
+{
+ dir_utime(ctx);
+ kfree(ctx->name_buf);
+ kfree(ctx->symlink_buf);
+ kfree(ctx->header_buf);
+}
--
2.30.2