Many of the Linux security/integrity features are dependent on file
metadata, stored as extended attributes (xattrs), for making decisions.
These features need to be initialized during initcall and enabled as
early as possible for complete security coverage.
Initramfs (tmpfs) supports xattrs, but newc CPIO archive format does not
support including them into the archive.
There are several ways to include xattrs for initramfs:
- Add TAR support. Complicated format and big headers looks like too
much overhead.
- Include a file manifest containing the xattrs in the CPIO. Should be
easy for initramfs because we can set xattrs at the end when all files
are extracted, but extracting such archive in userspace will be more
complicated. For example it may be necessary to set SELinux labels for
directories before extracting files into them, so manifest has to be
extracted first and then searched during each file extraction.
- Extend CPIO header to support xattrs. This seem to be the most
straight forward way. It also allows to do other useful changes
to CPIO format at the same time. E.g. increase filesize field to
support files >4GB.
This patch set extends the existing newc CPIO archive format to include
xattrs in the initramfs.
The series is based on v4.15-rc8. cpio_xattr branch is available here:
https://github.com/kontar/linux/commits/cpio_xattr
=== Patch summary ===
Documentation:
[PATCH 01/15] Documentation: add newcx initramfs format description
Refactoring to simplify adding the new format:
[PATCH 02/15] initramfs: replace states with function pointers
[PATCH 03/15] initramfs: store file name in name_buf
[PATCH 04/15] initramfs: remove unnecessary symlinks processing shortcut
[PATCH 05/15] initramfs: move files creation into separate state
[PATCH 06/15] initramfs: separate reading cpio method from header
[PATCH 07/15] initramfs: split header layout information from parsing
function
Parse newxc format:
[PATCH 08/15] initramfs: add newcx format
[PATCH 09/15] initramfs: set extended attributes
Generate newcx cpio archive:
[PATCH 10/15] gen_init_cpio: move header formatting into function
[PATCH 11/15] gen_init_cpio: add newcx format
[PATCH 12/15] gen_init_cpio: set extended attributes for newcx
[PATCH 13/15] gen_initramfs_list.sh: add -x option to enable newcx
SELinux patches used for testing. They will be sent to SELinux
maintainers separately.
[PATCH 14/15] selinux: allow setxattr on rootfs so initramfs code can
set them
[PATCH 15/15] selinux: delay sid population for rootfs till init is
complete
=== Testing ===
gen_initramfs_list.sh can be used to generate newcx CPIO archive: if
CONFIG_INITRAMFS_NEWCX is enabled CONFIG_INITRAMFS_SOURCE will be packed
into newcx archive. It is enough for basic testing, but it is not
convenient for more complex setup.
Victor have prepared a test setup with SELinux-labeled initramfs based
on Poky(Yocto) with meta-selinux layer.
Repo manifest and build instructions:
https://github.com/victorkamensky/initramfs-xattrs-manifest
Reference cpio utility patch to support newcx format could be
found as part of poky/meta-selinux testing environment at
https://raw.githubusercontent.com/victorkamensky/initramfs-xattrs-poky/rocko/meta/recipes-extended/cpio/cpio-2.12/cpio-xattrs.patch
=== History ===
The patch set is based on Mimi's series from Jan 2015:
https://www.mail-archive.com/[email protected]/msg03971.html
Latest discussion I was able to find is from Dec 2015:
https://www.mail-archive.com/[email protected]/msg04198.html
Format changes:
- increased size of filesize to 64 bits to support files >4GB.
- increased mtime field size to 64 bits to get usec precision
and more than 32 bits of seconds. Rob mentioned nsec, but that
will leave only 34 bits for seconds. If nsec precision is needed
then we can add a separate 32 bit nsec field (it will match statx).
- Checksum field is replased by xattrs_size field.
Other fields are left unchanged. See patch format description in the
patch #1.
James suggested to add many other changes, but that will bloat header
and make this new format very similar to tar. To keep format simple no
other fields were added.
v2 changes:
- added documentation
- made format more consistent. In previous version a sequence of fields
in newcx header was different for symlinks and regular files (for
symlinks data field was before xattrs). It was caused by a flow
shortcut during symlink entry parsing.
- removed unused checksum field in newcx header
- removed redundant xattrcount at the beginning of xattr section
(xattrs_size is enough to determine the end of section).
- size of xattr entry in xattr section includes both name and value.
This makes format more consistent and allows to jump over an entry
without scanning for the end of name string first.
- streamlined the state machine to address the previous issue and make
it easier to add the new format
- made header parsing data-driven to remove magic numbers and make it
easier to add the new format
- eliminated unnecessary buffer allocation for every file name
- pass xattrs to gen_init_cpio via cpio_list file instead of reading
them from files during packaging. This allows to set xattrs in CPIO
even if they can't be set on a build machine.
- incorporated several bug fixes from Victor Kamensky for v1 series
Mimi Zohar (3):
initramfs: separate reading cpio method from header
initramfs: set extended attributes
gen_initramfs_list.sh: add -x option to enable newcx format
Taras Kondratiuk (10):
Documentation: add newcx initramfs format description
initramfs: replace states with function pointers
initramfs: store file name in name_buf
initramfs: remove unnecessary symlinks processing shortcut
initramfs: move files creation into separate state
initramfs: split header layout information from parsing function
initramfs: add newcx format
gen_init_cpio: move header formatting into function
gen_init_cpio: add newcx format
gen_init_cpio: set extended attributes for newcx format
Victor Kamensky (2):
selinux: allow setxattr on rootfs so initramfs code can set them
selinux: delay sid population for rootfs till init is complete
Documentation/early-userspace/buffer-format.txt | 46 ++-
init/initramfs.c | 401 ++++++++++++++++++------
scripts/gen_initramfs_list.sh | 13 +-
security/selinux/hooks.c | 19 ++
security/selinux/include/security.h | 1 +
usr/Kconfig | 11 +
usr/Makefile | 3 +-
usr/gen_init_cpio.c | 364 ++++++++++++++-------
8 files changed, 626 insertions(+), 232 deletions(-)
--
2.10.3.dirty
Special handling of symlinks in do_header() assumes that name and body
entries are sequential and reads them together. This shortcut has no
real performance benefits, but it complicates changes to the state
machine.
Make handling of symlinks more similar to a regular files. Store name
in name_buf and destination in symlink_buf.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
init/initramfs.c | 29 +++++++++++++----------------
1 file changed, 13 insertions(+), 16 deletions(-)
diff --git a/init/initramfs.c b/init/initramfs.c
index b6ee675e5cdb..d0ab7ad6ac05 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -266,16 +266,7 @@ static int __init do_header(void)
state = do_skip;
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 = do_symlink;
- state = do_collect;
- return 0;
- }
- if (S_ISREG(mode) || !body_len)
+ if (S_ISREG(mode) || S_ISLNK(mode) || !body_len)
read_into(name_buf, N_ALIGN(name_len), do_name);
return 0;
}
@@ -372,6 +363,11 @@ static int __init do_name(void)
sys_chmod(collected, mode);
do_utime(collected, mtime);
}
+ } else if (S_ISLNK(mode)) {
+ if (body_len > PATH_MAX)
+ return 0;
+ memcpy_optional(name_buf, collected, N_ALIGN(name_len));
+ read_into(symlink_buf, body_len, do_symlink);
}
return 0;
}
@@ -397,11 +393,12 @@ static int __init do_copy(void)
static int __init do_symlink(void)
{
- collected[N_ALIGN(name_len) + body_len] = '\0';
- clean_path(collected, 0);
- sys_symlink(collected + N_ALIGN(name_len), collected);
- sys_lchown(collected, uid, gid);
- do_utime(collected, mtime);
+ memcpy_optional(symlink_buf, collected, body_len);
+ symlink_buf[body_len] = '\0';
+ clean_path(name_buf, 0);
+ sys_symlink(symlink_buf, name_buf);
+ sys_lchown(name_buf, uid, gid);
+ do_utime(name_buf, mtime);
state = do_skip;
next_state = do_reset;
return 0;
@@ -453,7 +450,7 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
static __initdata char msg_buf[64];
header_buf = kmalloc(110, GFP_KERNEL);
- symlink_buf = kmalloc(PATH_MAX + N_ALIGN(PATH_MAX) + 1, GFP_KERNEL);
+ symlink_buf = kmalloc(PATH_MAX + 1, GFP_KERNEL);
name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
if (!header_buf || !symlink_buf || !name_buf)
--
2.10.3.dirty
From: Mimi Zohar <[email protected]>
-x option populates extended attributes in cpio_list file passed to
get_init_cpio and selects newcx CPIO format.
Signed-off-by: Mimi Zohar <[email protected]>
Signed-off-by: Taras Kondratiuk <[email protected]>
---
scripts/gen_initramfs_list.sh | 13 ++++++++++++-
usr/Kconfig | 11 +++++++++++
usr/Makefile | 3 ++-
3 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/scripts/gen_initramfs_list.sh b/scripts/gen_initramfs_list.sh
index 86a3c0e5cfbc..cddb82f093d9 100755
--- a/scripts/gen_initramfs_list.sh
+++ b/scripts/gen_initramfs_list.sh
@@ -24,6 +24,7 @@ $0 [-o <file>] [-u <uid>] [-g <gid>] {-d | <cpio_source>} ...
-g <gid> Group ID to map to group ID 0 (root).
<gid> is only meaningful if <cpio_source> is a
directory. "squash" forces all files to gid 0.
+ -x include file extended attributes in cpio archive.
<cpio_source> File list or directory for cpio archive.
If <cpio_source> is a .cpio file it will be used
as direct input to initramfs.
@@ -146,6 +147,9 @@ parse() {
;;
esac
+ $include_xattrs && \
+ getfattr -h -d -m - -e hex --absolute-names ${location} | \
+ sed -e '/^#/d' -e '/^$/d' -e 's/^/xattr /' >> ${output}
echo "${str}" >> ${output}
return 0
@@ -226,6 +230,8 @@ root_gid=0
dep_list=
cpio_file=
cpio_list=
+cpio_opts=
+include_xattrs=false
output="/dev/stdout"
output_file=""
is_cpio_compressed=
@@ -283,6 +289,10 @@ while [ $# -gt 0 ]; do
default_list="$arg"
${dep_list}default_initramfs
;;
+ "-x") # include extended attributers
+ cpio_opts="-x"
+ include_xattrs=true
+ ;;
"-h")
usage
exit 0
@@ -312,7 +322,8 @@ if [ ! -z ${output_file} ]; then
fi
fi
cpio_tfile="$(mktemp ${TMPDIR:-/tmp}/cpiofile.XXXXXX)"
- usr/gen_init_cpio $timestamp ${cpio_list} > ${cpio_tfile}
+ usr/gen_init_cpio $timestamp ${cpio_opts} ${cpio_list} \
+ > ${cpio_tfile}
else
cpio_tfile=${cpio_file}
fi
diff --git a/usr/Kconfig b/usr/Kconfig
index 43658b8a975e..0cc03bc4614c 100644
--- a/usr/Kconfig
+++ b/usr/Kconfig
@@ -52,6 +52,17 @@ config INITRAMFS_ROOT_GID
If you are not sure, leave it set to "0".
+config INITRAMFS_NEWCX
+ bool "Use newcx CPIO format for initramfs"
+ depends on INITRAMFS_SOURCE!=""
+ default n
+ help
+ If selected "usr/gen_init_cpio" will generate newcx CPIO archive
+ format that supports extended attributes.
+
+ See <file:Documentation/early-userspace/buffer-format.txt> for
+ more details.
+
config RD_GZIP
bool "Support initial ramdisk/ramfs compressed using gzip"
depends on BLK_DEV_INITRD
diff --git a/usr/Makefile b/usr/Makefile
index 237a028693ce..1106bfd61475 100644
--- a/usr/Makefile
+++ b/usr/Makefile
@@ -29,7 +29,8 @@ ramfs-input := $(if $(filter-out "",$(CONFIG_INITRAMFS_SOURCE)), \
$(shell echo $(CONFIG_INITRAMFS_SOURCE)),-d)
ramfs-args := \
$(if $(CONFIG_INITRAMFS_ROOT_UID), -u $(CONFIG_INITRAMFS_ROOT_UID)) \
- $(if $(CONFIG_INITRAMFS_ROOT_GID), -g $(CONFIG_INITRAMFS_ROOT_GID))
+ $(if $(CONFIG_INITRAMFS_ROOT_GID), -g $(CONFIG_INITRAMFS_ROOT_GID)) \
+ $(if $(CONFIG_INITRAMFS_NEWCX), -x)
# $(datafile_d_y) is used to identify all files included
# in initramfs and to detect if any files are added/removed.
--
2.10.3.dirty
Add "newcx" format that supports extended attributes and has increased
size of c_mtime and c_filesize fields.
Added -x option to select "newcx" format. Default is "newc".
Refer to Documentation/early-userspace/buffer-format.txt for detailed
format description.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
usr/gen_init_cpio.c | 70 +++++++++++++++++++++++++++++++++++++----------------
1 file changed, 49 insertions(+), 21 deletions(-)
diff --git a/usr/gen_init_cpio.c b/usr/gen_init_cpio.c
index 7a2a6d85345d..78a47a5bdcb1 100644
--- a/usr/gen_init_cpio.c
+++ b/usr/gen_init_cpio.c
@@ -10,6 +10,7 @@
#include <errno.h>
#include <ctype.h>
#include <limits.h>
+#include <assert.h>
/*
* Original work by Jeff Garzik
@@ -21,6 +22,8 @@
#define xstr(s) #s
#define str(s) xstr(s)
+static int newcx;
+static unsigned int cpio_hdr_size;
static unsigned int offset;
static unsigned int ino = 721;
static time_t default_mtime;
@@ -56,7 +59,7 @@ static void push_rest(const char *name)
putchar(0);
offset += name_len;
- tmp_ofs = name_len + 110;
+ tmp_ofs = name_len + cpio_hdr_size;
while (tmp_ofs & 3) {
putchar(0);
offset++;
@@ -77,6 +80,7 @@ struct cpio_header {
int rdevmajor;
int rdevminor;
size_t namesize;
+ size_t xattrsize;
unsigned int check;
};
@@ -84,24 +88,44 @@ static void push_hdr(const struct cpio_header *hdr)
{
char s[256];
- sprintf(s, "%s%08X%08X%08lX%08lX%08X%08lX"
- "%08X%08X%08X%08X%08X%08X%08X",
- "070701",
- hdr->ino,
- hdr->mode,
- (long)hdr->uid,
- (long)hdr->gid,
- hdr->nlink,
- (long)hdr->mtime,
- (unsigned int)hdr->filesize,
- hdr->devmajor,
- hdr->devminor,
- hdr->rdevmajor,
- hdr->rdevminor,
- (unsigned int)hdr->namesize,
- hdr->check);
+ if (newcx) {
+ sprintf(s, "%s%08X%08X%08lX%08lX%08X%016llX"
+ "%016llX%08X%08X%08X%08X%08X%08X",
+ "070703",
+ hdr->ino,
+ hdr->mode,
+ (long)hdr->uid,
+ (long)hdr->gid,
+ hdr->nlink,
+ hdr->mtime * 1000000ULL,
+ (long long)hdr->filesize,
+ hdr->devmajor,
+ hdr->devminor,
+ hdr->rdevmajor,
+ hdr->rdevminor,
+ (unsigned int)hdr->namesize,
+ (unsigned int)hdr->xattrsize);
+ } else {
+ sprintf(s, "%s%08X%08X%08lX%08lX%08X%08lX"
+ "%08X%08X%08X%08X%08X%08X%08X",
+ "070701",
+ hdr->ino,
+ hdr->mode,
+ (long)hdr->uid,
+ (long)hdr->gid,
+ hdr->nlink,
+ (long)hdr->mtime,
+ (unsigned int)hdr->filesize,
+ hdr->devmajor,
+ hdr->devminor,
+ hdr->rdevmajor,
+ hdr->rdevminor,
+ (unsigned int)hdr->namesize,
+ hdr->check);
+ }
fputs(s, stdout);
- offset += 110;
+ assert((offset & 3) == 0);
+ offset += cpio_hdr_size;
}
static void cpio_trailer(void)
@@ -301,7 +325,7 @@ static int cpio_mkfile(const char *name, const char *location,
{
char *filebuf = NULL;
struct stat buf;
- long size;
+ size_t size;
int file = -1;
int retval;
int rc = -1;
@@ -450,7 +474,7 @@ static int cpio_mkfile_line(const char *line)
static void usage(const char *prog)
{
fprintf(stderr, "Usage:\n"
- "\t%s [-t <timestamp>] <cpio_list>\n"
+ "\t%s [-t <timestamp>] [-x] <cpio_list>\n"
"\n"
"<cpio_list> is a file containing newline separated entries that\n"
"describe the files to be included in the initramfs archive:\n"
@@ -527,7 +551,7 @@ int main (int argc, char *argv[])
default_mtime = time(NULL);
while (1) {
- int opt = getopt(argc, argv, "t:h");
+ int opt = getopt(argc, argv, "t:h:x");
char *invalid;
if (opt == -1)
@@ -542,12 +566,16 @@ int main (int argc, char *argv[])
exit(1);
}
break;
+ case 'x':
+ newcx = 1;
+ break;
case 'h':
case '?':
usage(argv[0]);
exit(opt == 'h' ? 0 : 1);
}
}
+ cpio_hdr_size = newcx ? 134 : 110;
if (argc - optind != 1) {
usage(argv[0]);
--
2.10.3.dirty
gen_init_cpio creates CPIO archive according to cpio_list manifest file
that contains list of archive entries (one per line). To be able to
store extended attributes in newcx CPIO format we need to pass them via
cpio_list file.
One way of doing it would be to append xattrs to each entry line, but
"file" lines have a variable number of elements because of hardlinks. It
is not obvious how to mark end of hardlinks and start of xattrs in this
case.
This patch introduces a new entry type: "xattr". Each "xattr" line
specify one name=value pair. xattr values are applied to the next
non-xattr line. There can be multiple "xattr" lines before non-xattr
line.
It may be more logical to have xattr lines after corresponding
file entry, but it makes parsing a bit more complex and needs more
intrusive changes.
Xattr value is hex-encoded (see getfattr(1)). Plain string variant would
be easier to read, but special symbols have to be escaped. Hex encoding
is much simpler.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
usr/gen_init_cpio.c | 142 +++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 119 insertions(+), 23 deletions(-)
diff --git a/usr/gen_init_cpio.c b/usr/gen_init_cpio.c
index 78a47a5bdcb1..e356f9e532a2 100644
--- a/usr/gen_init_cpio.c
+++ b/usr/gen_init_cpio.c
@@ -10,6 +10,7 @@
#include <errno.h>
#include <ctype.h>
#include <limits.h>
+#include <sys/xattr.h>
#include <assert.h>
/*
@@ -50,21 +51,10 @@ static void push_pad (void)
}
}
-static void push_rest(const char *name)
+static void push_string_padded(const char *name)
{
- unsigned int name_len = strlen(name) + 1;
- unsigned int tmp_ofs;
-
- fputs(name, stdout);
- putchar(0);
- offset += name_len;
-
- tmp_ofs = name_len + cpio_hdr_size;
- while (tmp_ofs & 3) {
- putchar(0);
- offset++;
- tmp_ofs++;
- }
+ push_string(name);
+ push_pad();
}
struct cpio_header {
@@ -137,7 +127,7 @@ static void cpio_trailer(void)
};
push_hdr(&hdr);
- push_rest(name);
+ push_string_padded(name);
while (offset % 512) {
putchar(0);
@@ -145,6 +135,96 @@ static void cpio_trailer(void)
}
}
+struct xattr_hdr {
+ char c_size[8]; /* total size including c_size field */
+ char c_data[];
+};
+static unsigned int xattr_buflen;
+static char xattr_buf[4096];
+
+static void push_xattrs(void)
+{
+ if (!newcx || !xattr_buflen)
+ return;
+
+ if (fwrite(xattr_buf, xattr_buflen, 1, stdout) != 1)
+ fprintf(stderr, "writing xattrs failed\n");
+ offset += xattr_buflen;
+ xattr_buflen = 0;
+
+ push_pad();
+}
+
+static int convert_hex_string(const char *hex_str, char *out, size_t out_size)
+{
+ char buf[3];
+ size_t str_len = strlen(hex_str);
+
+ if (str_len % 2 != 0 || str_len / 2 > out_size)
+ return 0;
+
+ buf[2] = '\0';
+ while (*hex_str != '\0') {
+ buf[0] = *hex_str++;
+ buf[1] = *hex_str++;
+ *out++ = (char)strtol(buf, NULL, 16);
+ }
+
+ return str_len / 2;
+}
+
+static int collect_xattr(const char *line)
+{
+ const char *name, *value;
+ size_t name_len, value_len;
+ char *buf = xattr_buf + xattr_buflen;
+ struct xattr_hdr *hdr = (struct xattr_hdr *)buf;
+ char *bufend = xattr_buf + sizeof(xattr_buf);
+ char *value_buf;
+ size_t xattr_entry_size;
+ char size_str[sizeof(hdr->c_size) + 1];
+
+ if (!newcx)
+ return 0;
+
+ name = line;
+ value = strchr(line, '=');
+ if (!value) {
+ fprintf(stderr, "Unrecognized xattr format '%s'", line);
+ return -1;
+ }
+ name_len = value - name;
+ value++;
+
+ /*
+ * For now we support only hex encoded values.
+ * String or base64 can be added later.
+ */
+ if (strncmp(value, "0x", 2)) {
+ fprintf(stderr,
+ "Only hex encoded xattr value is supported '%s'",
+ value);
+ return -1;
+ }
+
+ value += 2;
+ value_buf = buf + sizeof(struct xattr_hdr) + name_len + 1;
+ value_len = convert_hex_string(value, value_buf, bufend - value_buf);
+ if (value_len == 0) {
+ fprintf(stderr, "Failed to parse xattr value '%s'", line);
+ return -1;
+ }
+ xattr_entry_size = sizeof(struct xattr_hdr) + name_len + 1 + value_len;
+
+ sprintf(size_str, "%08X", (unsigned int)xattr_entry_size);
+ memcpy(hdr->c_size, size_str, sizeof(hdr->c_size));
+ memcpy(hdr->c_data, name, name_len);
+ hdr->c_data[name_len] = '\0';
+ xattr_buflen += xattr_entry_size;
+
+ return 0;
+}
+
static int cpio_mkslink(const char *name, const char *target,
unsigned int mode, uid_t uid, gid_t gid)
{
@@ -161,12 +241,12 @@ static int cpio_mkslink(const char *name, const char *target,
.devmajor = 3,
.devminor = 1,
.namesize = strlen(name)+1,
+ .xattrsize = xattr_buflen,
};
push_hdr(&hdr);
- push_string(name);
- push_pad();
- push_string(target);
- push_pad();
+ push_string_padded(name);
+ push_xattrs();
+ push_string_padded(target);
return 0;
}
@@ -203,9 +283,11 @@ static int cpio_mkgeneric(const char *name, unsigned int mode,
.devmajor = 3,
.devminor = 1,
.namesize = strlen(name)+1,
+ .xattrsize = xattr_buflen,
};
push_hdr(&hdr);
- push_rest(name);
+ push_string_padded(name);
+ push_xattrs();
return 0;
}
@@ -292,9 +374,11 @@ static int cpio_mknod(const char *name, unsigned int mode,
.rdevmajor = maj,
.rdevminor = min,
.namesize = strlen(name)+1,
+ .xattrsize = xattr_buflen,
};
push_hdr(&hdr);
- push_rest(name);
+ push_string_padded(name);
+ push_xattrs();
return 0;
}
@@ -377,10 +461,13 @@ static int cpio_mkfile(const char *name, const char *location,
.devmajor = 3,
.devminor = 1,
.namesize = namesize,
+ /* xattrs go on last link */
+ .xattrsize = (i == nlinks) ? xattr_buflen : 0,
};
push_hdr(&hdr);
- push_string(name);
- push_pad();
+ push_string_padded(name);
+ if (hdr.xattrsize)
+ push_xattrs();
if (size) {
if (fwrite(filebuf, size, 1, stdout) != 1) {
@@ -486,6 +573,8 @@ static void usage(const char *prog)
"slink <name> <target> <mode> <uid> <gid>\n"
"pipe <name> <mode> <uid> <gid>\n"
"sock <name> <mode> <uid> <gid>\n"
+ "# xattr line is applied to the next non-xattr entry\n"
+ "xattr <xattr_name>=<xattr_val>\n"
"\n"
"<name> name of the file/dir/nod/etc in the archive\n"
"<location> location of the file in the current filesystem\n"
@@ -498,12 +587,16 @@ static void usage(const char *prog)
"<maj> major number of nod\n"
"<min> minor number of nod\n"
"<hard links> space separated list of other links to file\n"
+ "<xattr_name> extended attribute name\n"
+ "<xattr_val> hex-encoded extended attribute value\n"
"\n"
"example:\n"
"# A simple initramfs\n"
"dir /dev 0755 0 0\n"
"nod /dev/console 0600 0 0 c 5 1\n"
"dir /root 0700 0 0\n"
+ "# set SELinux label 'system_u:object_r:bin_t:s0' for /sbin directory\n"
+ "xattr security.selinux=0x73797374656d5f753a6f626a6563745f723a62696e5f743a733000\n"
"dir /sbin 0755 0 0\n"
"file /sbin/kinit /usr/src/klibc/kinit/kinit 0755 0 0\n"
"\n"
@@ -533,6 +626,9 @@ struct file_handler file_handler_table[] = {
.type = "sock",
.handler = cpio_mksock_line,
}, {
+ .type = "xattr",
+ .handler = collect_xattr,
+ }, {
.type = NULL,
.handler = NULL,
}
--
2.10.3.dirty
Move most of the file creation logic into a separate state. This splits
collection of data stage from data processing and makes it easier to add
additional states for a new archive format.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
init/initramfs.c | 52 ++++++++++++++++++++++++++++++----------------------
1 file changed, 30 insertions(+), 22 deletions(-)
diff --git a/init/initramfs.c b/init/initramfs.c
index d0ab7ad6ac05..2d5920c094e0 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -192,6 +192,7 @@ static int __init do_collect(void);
static int __init do_header(void);
static int __init do_skip(void);
static int __init do_name(void);
+static int __init do_create(void);
static int __init do_copy(void);
static int __init do_symlink(void);
static int __init do_reset(void);
@@ -292,12 +293,12 @@ static int __init do_reset(void)
return 1;
}
-static int __init maybe_link(void)
+static int __init maybe_link(char *name)
{
if (nlink >= 2) {
- char *old = find_link(major, minor, ino, mode, collected);
+ char *old = find_link(major, minor, ino, mode, name);
if (old)
- return (sys_link(old, collected) < 0) ? -1 : 1;
+ return (sys_link(old, name) < 0) ? -1 : 1;
}
return 0;
}
@@ -321,52 +322,59 @@ static void *memcpy_optional(void *dest, const void *src, size_t n)
return dest;
}
-static __initdata int wfd;
-
static int __init do_name(void)
{
- state = do_skip;
- next_state = do_reset;
if (strcmp(collected, "TRAILER!!!") == 0) {
+ state = do_skip;
+ next_state = do_reset;
free_hash();
return 0;
}
- clean_path(collected, mode);
+ memcpy_optional(name_buf, collected, N_ALIGN(name_len));
+ state = do_create;
+ return 0;
+}
+
+
+static __initdata int wfd;
+
+static int __init do_create(void)
+{
+ state = do_skip;
+ next_state = do_reset;
+ clean_path(name_buf, mode);
if (S_ISREG(mode)) {
- int ml = maybe_link();
+ int ml = maybe_link(name_buf);
if (ml >= 0) {
int openflags = O_WRONLY|O_CREAT;
if (ml != 1)
openflags |= O_TRUNC;
- wfd = sys_open(collected, openflags, mode);
+ wfd = sys_open(name_buf, openflags, mode);
if (wfd >= 0) {
sys_fchown(wfd, uid, gid);
sys_fchmod(wfd, mode);
if (body_len)
sys_ftruncate(wfd, body_len);
- memcpy_optional(name_buf, collected,
- N_ALIGN(name_len));
state = do_copy;
}
}
} else if (S_ISDIR(mode)) {
- sys_mkdir(collected, mode);
- sys_chown(collected, uid, gid);
- sys_chmod(collected, mode);
- dir_add(collected, mtime);
+ sys_mkdir(name_buf, mode);
+ sys_chown(name_buf, uid, gid);
+ sys_chmod(name_buf, mode);
+ dir_add(name_buf, mtime);
} else if (S_ISBLK(mode) || S_ISCHR(mode) ||
S_ISFIFO(mode) || S_ISSOCK(mode)) {
- if (maybe_link() == 0) {
- sys_mknod(collected, mode, rdev);
- sys_chown(collected, uid, gid);
- sys_chmod(collected, mode);
- do_utime(collected, mtime);
+ if (maybe_link(name_buf) == 0) {
+ sys_mknod(name_buf, mode, rdev);
+ sys_chown(name_buf, uid, gid);
+ sys_chmod(name_buf, mode);
+ do_utime(name_buf, mtime);
}
} else if (S_ISLNK(mode)) {
if (body_len > PATH_MAX)
return 0;
- memcpy_optional(name_buf, collected, N_ALIGN(name_len));
read_into(symlink_buf, body_len, do_symlink);
}
return 0;
--
2.10.3.dirty
From: Victor Kamensky <[email protected]>
With initramfs cpio format that supports extended attributes
we need to skip sid population on sys_lsetxattr call from
initramfs for rootfs if security server is not initialized yet.
Otherwise callback in selinux_inode_post_setxattr will try to
translate give security.selinux label into sid context and since
security server is not available yet inode will receive default
sid (typically kernel_t). Note that in the same time proper
label will be stored in inode xattrs. Later, since inode sid
would be already populated system will never look back at
actual xattrs. But if we skip sid population for rootfs and
we have policy that direct use of xattrs for rootfs, proper
sid will be filled in from extended attributes one node is
accessed and server is initialized.
Note new DELAYAFTERINIT_MNT super block flag is introduced
to only mark rootfs for such behavior. For other types of
tmpfs original logic is still used.
Signed-off-by: Victor Kamensky <[email protected]>
---
security/selinux/hooks.c | 9 ++++++++-
security/selinux/include/security.h | 1 +
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index f3fe65589f02..bb25268f734e 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -716,7 +716,7 @@ static int selinux_set_mnt_opts(struct super_block *sb,
*/
if (!strncmp(sb->s_type->name, "rootfs",
sizeof("rootfs")))
- sbsec->flags |= SBLABEL_MNT;
+ sbsec->flags |= SBLABEL_MNT|DELAYAFTERINIT_MNT;
/* Defer initialization until selinux_complete_init,
after the initial policy is loaded and the security
@@ -3253,6 +3253,7 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name,
{
struct inode *inode = d_backing_inode(dentry);
struct inode_security_struct *isec;
+ struct superblock_security_struct *sbsec;
u32 newsid;
int rc;
@@ -3261,6 +3262,12 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name,
return;
}
+ if (!ss_initialized) {
+ sbsec = inode->i_sb->s_security;
+ if (sbsec->flags & DELAYAFTERINIT_MNT)
+ return;
+ }
+
rc = security_context_to_sid_force(value, size, &newsid);
if (rc) {
printk(KERN_ERR "SELinux: unable to map context to SID"
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index 02f0412d42f2..585acfd6cbcf 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -52,6 +52,7 @@
#define ROOTCONTEXT_MNT 0x04
#define DEFCONTEXT_MNT 0x08
#define SBLABEL_MNT 0x10
+#define DELAYAFTERINIT_MNT 0x20
/* Non-mount related flags */
#define SE_SBINITIALIZED 0x0100
#define SE_SBPROC 0x0200
--
2.10.3.dirty
From: Mimi Zohar <[email protected]>
This patch writes out the extended attributes included in the cpio file.
As the "security.ima" xattr needs to be written after the file data.
this patch separates extracting and setting the xattrs by defining new
do_setxattrs state.
[kamensky: fixed restoring of xattrs for symbolic links by using
sys_lsetxattr() instead of sys_setxattr()]
Signed-off-by: Mimi Zohar <[email protected]>
Signed-off-by: Victor Kamensky <[email protected]>
Signed-off-by: Taras Kondratiuk <[email protected]>
---
init/initramfs.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 52 insertions(+), 5 deletions(-)
diff --git a/init/initramfs.c b/init/initramfs.c
index 3d0f46c28459..040e26cf451a 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -310,6 +310,7 @@ static int __init do_xattrs(void);
static int __init do_create(void);
static int __init do_copy(void);
static int __init do_symlink(void);
+static int __init do_setxattrs(void);
static int __init do_reset(void);
typedef int (*fsm_state_t)(void);
@@ -472,7 +473,7 @@ static int __init do_name(void)
static int __init do_xattrs(void)
{
- /* Do nothing for now */
+ memcpy_optional(xattr_buf, collected, xattr_len);
state = do_create;
return 0;
}
@@ -481,8 +482,7 @@ static __initdata int wfd;
static int __init do_create(void)
{
- state = do_skip;
- next_state = do_reset;
+ state = do_setxattrs;
clean_path(name_buf, mode);
if (S_ISREG(mode)) {
int ml = maybe_link(name_buf);
@@ -515,8 +515,11 @@ static int __init do_create(void)
do_utime(name_buf, mtime);
}
} else if (S_ISLNK(mode)) {
- if (body_len > PATH_MAX)
+ if (body_len > PATH_MAX) {
+ state = do_skip;
+ next_state = do_reset;
return 0;
+ }
read_into(symlink_buf, body_len, do_symlink);
}
return 0;
@@ -530,7 +533,7 @@ static int __init do_copy(void)
sys_close(wfd);
do_utime(name_buf, mtime);
eat(body_len);
- state = do_skip;
+ state = do_setxattrs;
return 0;
} else {
if (xwrite(wfd, victim, byte_count) != byte_count)
@@ -549,8 +552,52 @@ static int __init do_symlink(void)
sys_symlink(symlink_buf, name_buf);
sys_lchown(name_buf, uid, gid);
do_utime(name_buf, mtime);
+ state = do_setxattrs;
+ return 0;
+}
+
+struct xattr_hdr {
+ char c_size[8]; /* total size including c_size field */
+ char c_data[]; /* <name>\0<value> */
+};
+
+static int __init do_setxattrs(void)
+{
+ char *buf = xattr_buf;
+ char *bufend = buf + xattr_len;
+ struct xattr_hdr *hdr;
+ char str[sizeof(hdr->c_size) + 1];
+
state = do_skip;
next_state = do_reset;
+ if (!xattr_len)
+ return 0;
+
+ str[sizeof(hdr->c_size)] = 0;
+
+ while (buf < bufend) {
+ char *xattr_name, *xattr_value;
+ unsigned long xattr_entry_size, xattr_value_size;
+ int ret;
+
+ hdr = (struct xattr_hdr *)buf;
+ memcpy(str, hdr->c_size, sizeof(hdr->c_size));
+ ret = kstrtoul(str, 16, &xattr_entry_size);
+ buf += xattr_entry_size;
+ if (ret || buf > bufend) {
+ error("malformed xattrs");
+ break;
+ }
+
+ xattr_name = hdr->c_data;
+ xattr_value = xattr_name + strlen(xattr_name) + 1;
+ xattr_value_size = buf - xattr_value;
+
+ ret = sys_lsetxattr(name_buf, xattr_name, xattr_value,
+ xattr_value_size, 0);
+ pr_debug("%s: %s size: %lu val: %s (ret: %d)\n", name_buf,
+ xattr_name, xattr_value_size, xattr_value, ret);
+ }
return 0;
}
--
2.10.3.dirty
Add 'newcx' format that adds extended attributes and increased size of
c_mtime and c_filesize fields.
Refer to Documentation/early-userspace/buffer-format.txt for detailed
format description.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
init/initramfs.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 85 insertions(+), 10 deletions(-)
diff --git a/init/initramfs.c b/init/initramfs.c
index 7f0bbfde94e3..3d0f46c28459 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -54,6 +54,7 @@ static void __init error(char *x)
/* link hash */
#define N_ALIGN(len) ((((len) + 1) & ~3) + 2)
+#define X_ALIGN(len) ((len + 3) & ~3)
static __initdata struct hash {
int ino, minor, major;
@@ -154,7 +155,8 @@ static void __init dir_utime(void)
static __initdata time64_t mtime;
static __initdata u32 ino, major, minor, nlink, rmajor, rminor;
static __initdata umode_t mode;
-static __initdata u32 body_len, name_len;
+static __initdata u32 name_len, xattr_len;
+static __initdata u64 body_len, mtime_u64;
static __initdata uid_t uid;
static __initdata gid_t gid;
static __initdata u32 mode_u32;
@@ -167,6 +169,12 @@ struct cpio_hdr_field {
const char *name;
};
+static __initdata enum cpio_format {
+ CPIO_NO_MAGIC,
+ CPIO_NEWC,
+ CPIO_NEWCX,
+} cpio_format;
+
#define HDR_FIELD(type, field, variable) \
{ .offset = offsetof(type, field) + \
BUILD_BUG_ON_ZERO(sizeof(*(variable))*2 < FIELD_SIZEOF(type, field)),\
@@ -177,9 +185,11 @@ struct cpio_hdr_field {
#define NEWC_FIELD(field, variable) \
HDR_FIELD(struct cpio_newc_header, field, variable)
+#define NEWCX_FIELD(field, variable) \
+ HDR_FIELD(struct cpio_newcx_header, field, variable)
-#define CPIO_MAX_HEADER_SIZE sizeof(struct cpio_newc_header)
-#define CPIO_MAX_FIELD_SIZE 8
+#define CPIO_MAX_HEADER_SIZE sizeof(struct cpio_newcx_header)
+#define CPIO_MAX_FIELD_SIZE 16
#define CPIO_MAGIC_SIZE 6
struct cpio_newc_header {
@@ -214,10 +224,44 @@ static struct cpio_hdr_field cpio_newc_header_info[] __initdata = {
{ 0 },
};
+struct cpio_newcx_header {
+ char c_ino[8];
+ char c_mode[8];
+ char c_uid[8];
+ char c_gid[8];
+ char c_nlink[8];
+ char c_mtime[16];
+ char c_filesize[16];
+ char c_devmajor[8];
+ char c_devminor[8];
+ char c_rdevmajor[8];
+ char c_rdevminor[8];
+ char c_namesize[8];
+ char c_xattrsize[8];
+};
+
+static struct cpio_hdr_field cpio_newcx_header_info[] __initdata = {
+ NEWCX_FIELD(c_ino, &ino),
+ NEWCX_FIELD(c_mode, &mode_u32),
+ NEWCX_FIELD(c_uid, &uid),
+ NEWCX_FIELD(c_gid, &gid),
+ NEWCX_FIELD(c_nlink, &nlink),
+ NEWCX_FIELD(c_mtime, &mtime_u64),
+ NEWCX_FIELD(c_filesize, &body_len),
+ NEWCX_FIELD(c_devmajor, &major),
+ NEWCX_FIELD(c_devminor, &minor),
+ NEWCX_FIELD(c_rdevmajor, &rmajor),
+ NEWCX_FIELD(c_rdevminor, &rminor),
+ NEWCX_FIELD(c_namesize, &name_len),
+ NEWCX_FIELD(c_xattrsize, &xattr_len),
+ { 0 },
+};
+
static void __init parse_header(char *s)
{
char buf[CPIO_MAX_FIELD_SIZE + 1];
- struct cpio_hdr_field *field = cpio_newc_header_info;
+ struct cpio_hdr_field *field = (cpio_format == CPIO_NEWC) ?
+ cpio_newc_header_info : cpio_newcx_header_info;
while (field->size) {
int ret = 0;
@@ -243,7 +287,15 @@ static void __init parse_header(char *s)
pr_err("invalid cpio header field (%d)", ret);
field++;
}
+
mode = mode_u32;
+ if (cpio_format == CPIO_NEWCX) {
+ /* Microseconds are ignored for now */
+ do_div(mtime_u64, USEC_PER_SEC);
+ mtime = mtime_u64;
+ } else {
+ xattr_len = 0;
+ }
}
/* FSM */
@@ -254,6 +306,7 @@ static int __init do_format(void);
static int __init do_header(void);
static int __init do_skip(void);
static int __init do_name(void);
+static int __init do_xattrs(void);
static int __init do_create(void);
static int __init do_copy(void);
static int __init do_symlink(void);
@@ -291,7 +344,7 @@ static void __init read_into(char *buf, unsigned size, fsm_state_t next)
}
}
-static __initdata char *header_buf, *symlink_buf, *name_buf;
+static __initdata char *header_buf, *symlink_buf, *name_buf, *xattr_buf;
static int __init do_start(void)
{
@@ -315,22 +368,34 @@ static int __init do_collect(void)
static int __init do_format(void)
{
- if (memcmp(collected, "070707", CPIO_MAGIC_SIZE) == 0) {
+ int header_size = 0;
+
+ cpio_format = CPIO_NO_MAGIC;
+
+ if (!memcmp(collected, "070707", CPIO_MAGIC_SIZE)) {
error("incorrect cpio method used: use -H newc option");
return 1;
+ } else if (!memcmp(collected, "070701", CPIO_MAGIC_SIZE)) {
+ cpio_format = CPIO_NEWC;
+ header_size = sizeof(struct cpio_newc_header);
+ } else if (!memcmp(collected, "070703", CPIO_MAGIC_SIZE)) {
+ cpio_format = CPIO_NEWCX;
+ header_size = sizeof(struct cpio_newcx_header);
}
- if (memcmp(collected, "070701", CPIO_MAGIC_SIZE)) {
+
+ if (cpio_format == CPIO_NO_MAGIC) {
error("no cpio magic");
return 1;
}
- read_into(header_buf, sizeof(struct cpio_newc_header), do_header);
+ read_into(header_buf, header_size, do_header);
return 0;
}
static int __init do_header(void)
{
parse_header(collected);
- next_header = this_header + N_ALIGN(name_len) + body_len;
+ next_header = this_header + N_ALIGN(name_len) + X_ALIGN(xattr_len) +
+ body_len;
next_header = (next_header + 3) & ~3;
state = do_skip;
if (name_len <= 0 || name_len > PATH_MAX)
@@ -400,9 +465,17 @@ static int __init do_name(void)
}
memcpy_optional(name_buf, collected, N_ALIGN(name_len));
state = do_create;
+ if (xattr_len > 0)
+ read_into(xattr_buf, X_ALIGN(xattr_len), do_xattrs);
return 0;
}
+static int __init do_xattrs(void)
+{
+ /* Do nothing for now */
+ state = do_create;
+ return 0;
+}
static __initdata int wfd;
@@ -529,8 +602,9 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
header_buf = kmalloc(CPIO_MAX_HEADER_SIZE, GFP_KERNEL);
symlink_buf = kmalloc(PATH_MAX + 1, GFP_KERNEL);
name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
+ xattr_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
- if (!header_buf || !symlink_buf || !name_buf)
+ if (!header_buf || !symlink_buf || !name_buf || !xattr_buf)
panic("can't allocate buffers");
state = do_start;
@@ -575,6 +649,7 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
len -= my_inptr;
}
dir_utime();
+ kfree(xattr_buf);
kfree(name_buf);
kfree(symlink_buf);
kfree(header_buf);
--
2.10.3.dirty
CPIO header is generated in multiple places with the same sprintf()
format string. Move formatting into a single function in preparation
to adding a new cpio format.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
usr/gen_init_cpio.c | 186 ++++++++++++++++++++++++++--------------------------
1 file changed, 92 insertions(+), 94 deletions(-)
diff --git a/usr/gen_init_cpio.c b/usr/gen_init_cpio.c
index 03b21189d58b..7a2a6d85345d 100644
--- a/usr/gen_init_cpio.c
+++ b/usr/gen_init_cpio.c
@@ -64,34 +64,55 @@ static void push_rest(const char *name)
}
}
-static void push_hdr(const char *s)
+struct cpio_header {
+ unsigned int ino;
+ unsigned int mode;
+ uid_t uid;
+ gid_t gid;
+ unsigned int nlink;
+ time_t mtime;
+ size_t filesize;
+ int devmajor;
+ int devminor;
+ int rdevmajor;
+ int rdevminor;
+ size_t namesize;
+ unsigned int check;
+};
+
+static void push_hdr(const struct cpio_header *hdr)
{
+ char s[256];
+
+ sprintf(s, "%s%08X%08X%08lX%08lX%08X%08lX"
+ "%08X%08X%08X%08X%08X%08X%08X",
+ "070701",
+ hdr->ino,
+ hdr->mode,
+ (long)hdr->uid,
+ (long)hdr->gid,
+ hdr->nlink,
+ (long)hdr->mtime,
+ (unsigned int)hdr->filesize,
+ hdr->devmajor,
+ hdr->devminor,
+ hdr->rdevmajor,
+ hdr->rdevminor,
+ (unsigned int)hdr->namesize,
+ hdr->check);
fputs(s, stdout);
offset += 110;
}
static void cpio_trailer(void)
{
- char s[256];
const char name[] = "TRAILER!!!";
+ struct cpio_header hdr = {
+ .nlink = 1,
+ .namesize = strlen(name)+1,
+ };
- sprintf(s, "%s%08X%08X%08lX%08lX%08X%08lX"
- "%08X%08X%08X%08X%08X%08X%08X",
- "070701", /* magic */
- 0, /* ino */
- 0, /* mode */
- (long) 0, /* uid */
- (long) 0, /* gid */
- 1, /* nlink */
- (long) 0, /* mtime */
- 0, /* filesize */
- 0, /* major */
- 0, /* minor */
- 0, /* rmajor */
- 0, /* rminor */
- (unsigned)strlen(name)+1, /* namesize */
- 0); /* chksum */
- push_hdr(s);
+ push_hdr(&hdr);
push_rest(name);
while (offset % 512) {
@@ -103,27 +124,21 @@ static void cpio_trailer(void)
static int cpio_mkslink(const char *name, const char *target,
unsigned int mode, uid_t uid, gid_t gid)
{
- char s[256];
-
if (name[0] == '/')
name++;
- sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
- "%08X%08X%08X%08X%08X%08X%08X",
- "070701", /* magic */
- ino++, /* ino */
- S_IFLNK | mode, /* mode */
- (long) uid, /* uid */
- (long) gid, /* gid */
- 1, /* nlink */
- (long) default_mtime, /* mtime */
- (unsigned)strlen(target)+1, /* filesize */
- 3, /* major */
- 1, /* minor */
- 0, /* rmajor */
- 0, /* rminor */
- (unsigned)strlen(name) + 1,/* namesize */
- 0); /* chksum */
- push_hdr(s);
+ struct cpio_header hdr = {
+ .ino = ino++,
+ .mode = S_IFLNK | mode,
+ .uid = uid,
+ .gid = gid,
+ .nlink = 1,
+ .mtime = default_mtime,
+ .filesize = strlen(target)+1,
+ .devmajor = 3,
+ .devminor = 1,
+ .namesize = strlen(name)+1,
+ };
+ push_hdr(&hdr);
push_string(name);
push_pad();
push_string(target);
@@ -152,27 +167,20 @@ static int cpio_mkslink_line(const char *line)
static int cpio_mkgeneric(const char *name, unsigned int mode,
uid_t uid, gid_t gid)
{
- char s[256];
-
if (name[0] == '/')
name++;
- sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
- "%08X%08X%08X%08X%08X%08X%08X",
- "070701", /* magic */
- ino++, /* ino */
- mode, /* mode */
- (long) uid, /* uid */
- (long) gid, /* gid */
- 2, /* nlink */
- (long) default_mtime, /* mtime */
- 0, /* filesize */
- 3, /* major */
- 1, /* minor */
- 0, /* rmajor */
- 0, /* rminor */
- (unsigned)strlen(name) + 1,/* namesize */
- 0); /* chksum */
- push_hdr(s);
+ struct cpio_header hdr = {
+ .ino = ino++,
+ .mode = mode,
+ .uid = uid,
+ .gid = gid,
+ .nlink = 2,
+ .mtime = default_mtime,
+ .devmajor = 3,
+ .devminor = 1,
+ .namesize = strlen(name)+1,
+ };
+ push_hdr(&hdr);
push_rest(name);
return 0;
}
@@ -241,8 +249,6 @@ static int cpio_mknod(const char *name, unsigned int mode,
uid_t uid, gid_t gid, char dev_type,
unsigned int maj, unsigned int min)
{
- char s[256];
-
if (dev_type == 'b')
mode |= S_IFBLK;
else
@@ -250,23 +256,20 @@ static int cpio_mknod(const char *name, unsigned int mode,
if (name[0] == '/')
name++;
- sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
- "%08X%08X%08X%08X%08X%08X%08X",
- "070701", /* magic */
- ino++, /* ino */
- mode, /* mode */
- (long) uid, /* uid */
- (long) gid, /* gid */
- 1, /* nlink */
- (long) default_mtime, /* mtime */
- 0, /* filesize */
- 3, /* major */
- 1, /* minor */
- maj, /* rmajor */
- min, /* rminor */
- (unsigned)strlen(name) + 1,/* namesize */
- 0); /* chksum */
- push_hdr(s);
+ struct cpio_header hdr = {
+ .ino = ino++,
+ .mode = mode,
+ .uid = uid,
+ .gid = gid,
+ .nlink = 1,
+ .mtime = default_mtime,
+ .devmajor = 3,
+ .devminor = 1,
+ .rdevmajor = maj,
+ .rdevminor = min,
+ .namesize = strlen(name)+1,
+ };
+ push_hdr(&hdr);
push_rest(name);
return 0;
}
@@ -296,7 +299,6 @@ static int cpio_mkfile(const char *name, const char *location,
unsigned int mode, uid_t uid, gid_t gid,
unsigned int nlinks)
{
- char s[256];
char *filebuf = NULL;
struct stat buf;
long size;
@@ -340,23 +342,19 @@ static int cpio_mkfile(const char *name, const char *location,
if (name[0] == '/')
name++;
namesize = strlen(name) + 1;
- sprintf(s,"%s%08X%08X%08lX%08lX%08X%08lX"
- "%08lX%08X%08X%08X%08X%08X%08X",
- "070701", /* magic */
- ino, /* ino */
- mode, /* mode */
- (long) uid, /* uid */
- (long) gid, /* gid */
- nlinks, /* nlink */
- (long) buf.st_mtime, /* mtime */
- size, /* filesize */
- 3, /* major */
- 1, /* minor */
- 0, /* rmajor */
- 0, /* rminor */
- namesize, /* namesize */
- 0); /* chksum */
- push_hdr(s);
+ struct cpio_header hdr = {
+ .ino = ino,
+ .mode = mode,
+ .uid = uid,
+ .gid = gid,
+ .nlink = nlinks,
+ .mtime = buf.st_mtime,
+ .filesize = size,
+ .devmajor = 3,
+ .devminor = 1,
+ .namesize = namesize,
+ };
+ push_hdr(&hdr);
push_string(name);
push_pad();
--
2.10.3.dirty
From: Mimi Zohar <[email protected]>
In preparation for adding xattr support, read the CPIO method
separately from the rest of the header.
Signed-off-by: Mimi Zohar <[email protected]>
Signed-off-by: Taras Kondratiuk <[email protected]>
---
init/initramfs.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/init/initramfs.c b/init/initramfs.c
index 2d5920c094e0..b3d39c8793be 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -168,7 +168,7 @@ static void __init parse_header(char *s)
int i;
buf[8] = '\0';
- for (i = 0, s += 6; i < 12; i++, s += 8) {
+ for (i = 0; i < 12; i++, s += 8) {
memcpy(buf, s, 8);
parsed[i] = simple_strtoul(buf, NULL, 16);
}
@@ -189,6 +189,7 @@ static void __init parse_header(char *s)
static int __init do_start(void);
static int __init do_collect(void);
+static int __init do_format(void);
static int __init do_header(void);
static int __init do_skip(void);
static int __init do_name(void);
@@ -233,7 +234,7 @@ static __initdata char *header_buf, *symlink_buf, *name_buf;
static int __init do_start(void)
{
- read_into(header_buf, 110, do_header);
+ read_into(header_buf, 6, do_format);
return 0;
}
@@ -251,7 +252,7 @@ static int __init do_collect(void)
return 0;
}
-static int __init do_header(void)
+static int __init do_format(void)
{
if (memcmp(collected, "070707", 6)==0) {
error("incorrect cpio method used: use -H newc option");
@@ -261,6 +262,12 @@ static int __init do_header(void)
error("no cpio magic");
return 1;
}
+ read_into(header_buf, 104, do_header);
+ return 0;
+}
+
+static int __init do_header(void)
+{
parse_header(collected);
next_header = this_header + N_ALIGN(name_len) + body_len;
next_header = (next_header + 3) & ~3;
@@ -457,7 +464,7 @@ 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);
+ header_buf = kmalloc(104, GFP_KERNEL);
symlink_buf = kmalloc(PATH_MAX + 1, GFP_KERNEL);
name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
--
2.10.3.dirty
From: Victor Kamensky <[email protected]>
initramfs code supporting extended cpio format have ability to
fill extended attributes from cpio archive, but if SELinux enabled
and security server is not initialized yet, selinux callback would
refuse setxattr made by initramfs code.
Solution enable SBLABEL_MNT on rootfs even if secrurity server is
not initialized yet.
Signed-off-by: Victor Kamensky <[email protected]>
---
security/selinux/hooks.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 8644d864e3c1..f3fe65589f02 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -706,6 +706,18 @@ static int selinux_set_mnt_opts(struct super_block *sb,
if (!ss_initialized) {
if (!num_opts) {
+ /*
+ * Special handling for rootfs. Is genfs but supports
+ * setting SELinux context on in-core inodes.
+ *
+ * Chicken and egg problem: policy may reside in rootfs
+ * but for initramfs code to fill in attributes, it
+ * needs selinux to allow that.
+ */
+ if (!strncmp(sb->s_type->name, "rootfs",
+ sizeof("rootfs")))
+ sbsec->flags |= SBLABEL_MNT;
+
/* Defer initialization until selinux_complete_init,
after the initial policy is loaded and the security
server is ready to handle calls. */
--
2.10.3.dirty
Many of the Linux security/integrity features are dependent on file
metadata, stored as extended attributes (xattrs), for making decisions.
These features need to be initialized during initcall and enabled as
early as possible for complete security coverage.
Initramfs (tmpfs) supports xattrs, but newc CPIO archive format does not
support including them into the archive.
This patch describes "extended" newc format (newcx) that is based on
newc and has following changes:
- extended attributes support
- increased size of filesize to support files >4GB.
- increased mtime field size to have usec precision and more than
32-bit of seconds.
- removed unused checksum field.
Signed-off-by: Taras Kondratiuk <[email protected]>
Signed-off-by: Mimi Zohar <[email protected]>
Signed-off-by: Victor Kamensky <[email protected]>
---
Documentation/early-userspace/buffer-format.txt | 46 ++++++++++++++++++++++---
1 file changed, 41 insertions(+), 5 deletions(-)
diff --git a/Documentation/early-userspace/buffer-format.txt b/Documentation/early-userspace/buffer-format.txt
index e1fd7f9dad16..d818df4f72dc 100644
--- a/Documentation/early-userspace/buffer-format.txt
+++ b/Documentation/early-userspace/buffer-format.txt
@@ -24,6 +24,7 @@ grammar, where:
+ indicates concatenation
GZIP() indicates the gzip(1) of the operand
ALGN(n) means padding with null bytes to an n-byte boundary
+ [n] means size of field is n bytes
initramfs := ("\0" | cpio_archive | cpio_gzip_archive)*
@@ -31,20 +32,29 @@ grammar, where:
cpio_archive := cpio_file* + (<nothing> | cpio_trailer)
- cpio_file := ALGN(4) + cpio_header + filename + "\0" + ALGN(4) + data
+ cpio_file := (cpio_newc_file | cpio_newcx_file)
+
+ cpio_newc_file := ALGN(4) + cpio_newc_header + filename + "\0" + \
+ ALGN(4) + data
+
+ cpio_newcx_file := ALGN(4) + cpio_newcx_header + filename + "\0" + \
+ ALGN(4) + xattrs + ALGN(4) + data
+
+ xattrs := xattr_entry*
+
+ xattr_entry := xattr_size[8] + xattr_name + "\0" + xattr_value
cpio_trailer := ALGN(4) + cpio_header + "TRAILER!!!\0" + ALGN(4)
In human terms, the initramfs buffer contains a collection of
-compressed and/or uncompressed cpio archives (in the "newc" or "crc"
-formats); arbitrary amounts zero bytes (for padding) can be added
-between members.
+compressed and/or uncompressed cpio archives; arbitrary amounts
+zero bytes (for padding) can be added between members.
The cpio "TRAILER!!!" entry (cpio end-of-archive) is optional, but is
not ignored; see "handling of hard links" below.
-The structure of the cpio_header is as follows (all fields contain
+The structure of the cpio_newc_header is as follows (all fields contain
hexadecimal ASCII numbers fully padded with '0' on the left to the
full width of the field, for example, the integer 4780 is represented
by the ASCII string "000012ac"):
@@ -81,6 +91,32 @@ algorithm used.
If the filename is "TRAILER!!!" this is actually an end-of-archive
marker; the c_filesize for an end-of-archive marker must be zero.
+"Extended" newc format (newcx)
+"newcx" cpio format extends "newc" by increasing size of some fields
+and adding extended attributes support. cpio_newcx_header structure:
+
+Field name Field size Meaning
+c_magic 6 bytes The string "070703"
+c_ino 8 bytes File inode number
+c_mode 8 bytes File mode and permissions
+c_uid 8 bytes File uid
+c_gid 8 bytes File gid
+c_nlink 8 bytes Number of links
+c_mtime 16 bytes Modification time (microseconds)
+c_filesize 16 bytes Size of data field
+c_maj 8 bytes Major part of file device number
+c_min 8 bytes Minor part of file device number
+c_rmaj 8 bytes Major part of device node reference
+c_rmin 8 bytes Minor part of device node reference
+c_namesize 8 bytes Length of filename, including final \0
+c_xattrs_size 8 bytes Size of xattrs field
+
+Most of the fields match cpio_newc_header except c_mtime that contains
+microseconds. c_chksum field is dropped.
+
+xattr_size is a total size of xattr_entry including 8 bytes of
+xattr_size. xattr_size has the same hexadecimal ASCII encoding as other
+fields of cpio header.
*** Handling of hard links
--
2.10.3.dirty
There is already name_buf buffer pre-allocated for a file name. No need
to allocate vcollected for every file. More over a name can be already
stored in name_buf by read_info() function.
Add memcpy_optional() function to handle such case.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
init/initramfs.c | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/init/initramfs.c b/init/initramfs.c
index 49cd2681a26f..b6ee675e5cdb 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -210,7 +210,6 @@ static inline void __init eat(unsigned n)
byte_count -= n;
}
-static __initdata char *vcollected;
static __initdata char *collected;
static long remains __initdata;
static __initdata char *collect;
@@ -324,6 +323,13 @@ static void __init clean_path(char *path, umode_t fmode)
}
}
+static void *memcpy_optional(void *dest, const void *src, size_t n)
+{
+ if (dest != src)
+ return memcpy(dest, src, n);
+ return dest;
+}
+
static __initdata int wfd;
static int __init do_name(void)
@@ -348,7 +354,8 @@ static int __init do_name(void)
sys_fchmod(wfd, mode);
if (body_len)
sys_ftruncate(wfd, body_len);
- vcollected = kstrdup(collected, GFP_KERNEL);
+ memcpy_optional(name_buf, collected,
+ N_ALIGN(name_len));
state = do_copy;
}
}
@@ -375,8 +382,7 @@ static int __init do_copy(void)
if (xwrite(wfd, victim, body_len) != body_len)
error("write error");
sys_close(wfd);
- do_utime(vcollected, mtime);
- kfree(vcollected);
+ do_utime(name_buf, mtime);
eat(body_len);
state = do_skip;
return 0;
--
2.10.3.dirty
Currently the FSM states are mapped directly to function pointers. Extra
level of intirection is not needed and makes navigation over the code
harder. One can't jump between states directly when browsing code (e.g.
with cscope). Need to go through actions[] array each time.
Replace states with their action function pointers. No behaviour change.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
init/initramfs.c | 73 +++++++++++++++++++++++++-------------------------------
1 file changed, 32 insertions(+), 41 deletions(-)
diff --git a/init/initramfs.c b/init/initramfs.c
index 7e99a0038942..49cd2681a26f 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -187,16 +187,17 @@ static void __init parse_header(char *s)
/* FSM */
-static __initdata enum state {
- Start,
- Collect,
- GotHeader,
- SkipIt,
- GotName,
- CopyFile,
- GotSymlink,
- Reset
-} state, next_state;
+static int __init do_start(void);
+static int __init do_collect(void);
+static int __init do_header(void);
+static int __init do_skip(void);
+static int __init do_name(void);
+static int __init do_copy(void);
+static int __init do_symlink(void);
+static int __init do_reset(void);
+
+typedef int (*fsm_state_t)(void);
+static __initdata fsm_state_t state, next_state;
static __initdata char *victim;
static unsigned long byte_count __initdata;
@@ -214,7 +215,7 @@ static __initdata char *collected;
static long remains __initdata;
static __initdata char *collect;
-static void __init read_into(char *buf, unsigned size, enum state next)
+static void __init read_into(char *buf, unsigned size, fsm_state_t next)
{
if (byte_count >= size) {
collected = victim;
@@ -224,7 +225,7 @@ static void __init read_into(char *buf, unsigned size, enum state next)
collect = collected = buf;
remains = size;
next_state = next;
- state = Collect;
+ state = do_collect;
}
}
@@ -232,7 +233,7 @@ static __initdata char *header_buf, *symlink_buf, *name_buf;
static int __init do_start(void)
{
- read_into(header_buf, 110, GotHeader);
+ read_into(header_buf, 110, do_header);
return 0;
}
@@ -263,7 +264,7 @@ static int __init do_header(void)
parse_header(collected);
next_header = this_header + N_ALIGN(name_len) + body_len;
next_header = (next_header + 3) & ~3;
- state = SkipIt;
+ state = do_skip;
if (name_len <= 0 || name_len > PATH_MAX)
return 0;
if (S_ISLNK(mode)) {
@@ -271,12 +272,12 @@ static int __init do_header(void)
return 0;
collect = collected = symlink_buf;
remains = N_ALIGN(name_len) + body_len;
- next_state = GotSymlink;
- state = Collect;
+ next_state = do_symlink;
+ state = do_collect;
return 0;
}
if (S_ISREG(mode) || !body_len)
- read_into(name_buf, N_ALIGN(name_len), GotName);
+ read_into(name_buf, N_ALIGN(name_len), do_name);
return 0;
}
@@ -327,8 +328,8 @@ static __initdata int wfd;
static int __init do_name(void)
{
- state = SkipIt;
- next_state = Reset;
+ state = do_skip;
+ next_state = do_reset;
if (strcmp(collected, "TRAILER!!!") == 0) {
free_hash();
return 0;
@@ -348,7 +349,7 @@ static int __init do_name(void)
if (body_len)
sys_ftruncate(wfd, body_len);
vcollected = kstrdup(collected, GFP_KERNEL);
- state = CopyFile;
+ state = do_copy;
}
}
} else if (S_ISDIR(mode)) {
@@ -377,7 +378,7 @@ static int __init do_copy(void)
do_utime(vcollected, mtime);
kfree(vcollected);
eat(body_len);
- state = SkipIt;
+ state = do_skip;
return 0;
} else {
if (xwrite(wfd, victim, byte_count) != byte_count)
@@ -395,29 +396,19 @@ static int __init do_symlink(void)
sys_symlink(collected + N_ALIGN(name_len), collected);
sys_lchown(collected, uid, gid);
do_utime(collected, mtime);
- state = SkipIt;
- next_state = Reset;
+ state = do_skip;
+ next_state = do_reset;
return 0;
}
-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)
{
byte_count = len;
victim = buf;
- while (!actions[state]())
- ;
+ do
+ pr_debug("state: %pf\n", state);
+ while (!state());
return len - byte_count;
}
@@ -433,11 +424,11 @@ static long __init flush_buffer(void *bufv, unsigned long len)
if (c == '0') {
buf += written;
len -= written;
- state = Start;
+ state = do_start;
} else if (c == 0) {
buf += written;
len -= written;
- state = Reset;
+ state = do_reset;
} else
error("junk in compressed archive");
}
@@ -462,13 +453,13 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
if (!header_buf || !symlink_buf || !name_buf)
panic("can't allocate buffers");
- state = Start;
+ state = do_start;
this_header = 0;
message = NULL;
while (!message && len) {
loff_t saved_offset = this_header;
if (*buf == '0' && !(this_header & 3)) {
- state = Start;
+ state = do_start;
written = write_buffer(buf, len);
buf += written;
len -= written;
@@ -497,7 +488,7 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
}
} else
error("junk in compressed archive");
- if (state != Reset)
+ if (state != do_reset)
error("junk in compressed archive");
this_header = saved_offset + my_inptr;
buf += my_inptr;
--
2.10.3.dirty
Header parsing has hardcoded assumption about header field size and
layout. It is hard to modify the function to parse a new format.
Move information about size and layout into a data structure to
make parsing code more generic and simplify adding a new format.
This also removes some magic numbers.
Signed-off-by: Taras Kondratiuk <[email protected]>
---
init/initramfs.c | 122 +++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 92 insertions(+), 30 deletions(-)
diff --git a/init/initramfs.c b/init/initramfs.c
index b3d39c8793be..7f0bbfde94e3 100644
--- a/init/initramfs.c
+++ b/init/initramfs.c
@@ -150,39 +150,100 @@ static void __init dir_utime(void)
}
}
-static __initdata time64_t mtime;
-
/* cpio header parsing */
-
-static __initdata unsigned long ino, major, minor, nlink;
+static __initdata time64_t mtime;
+static __initdata u32 ino, major, minor, nlink, rmajor, rminor;
static __initdata umode_t mode;
-static __initdata unsigned long body_len, name_len;
+static __initdata u32 body_len, name_len;
static __initdata uid_t uid;
static __initdata gid_t gid;
-static __initdata unsigned rdev;
+static __initdata u32 mode_u32;
+
+struct cpio_hdr_field {
+ size_t offset;
+ size_t size;
+ void *out;
+ size_t out_size;
+ const char *name;
+};
+
+#define HDR_FIELD(type, field, variable) \
+ { .offset = offsetof(type, field) + \
+ BUILD_BUG_ON_ZERO(sizeof(*(variable))*2 < FIELD_SIZEOF(type, field)),\
+ .size = FIELD_SIZEOF(type, field), \
+ .out = variable, \
+ .out_size = sizeof(*(variable)), \
+ .name = #field }
+
+#define NEWC_FIELD(field, variable) \
+ HDR_FIELD(struct cpio_newc_header, field, variable)
+
+#define CPIO_MAX_HEADER_SIZE sizeof(struct cpio_newc_header)
+#define CPIO_MAX_FIELD_SIZE 8
+#define CPIO_MAGIC_SIZE 6
+
+struct cpio_newc_header {
+ char c_ino[8];
+ char c_mode[8];
+ char c_uid[8];
+ char c_gid[8];
+ char c_nlink[8];
+ char c_mtime[8];
+ char c_filesize[8];
+ char c_devmajor[8];
+ char c_devminor[8];
+ char c_rdevmajor[8];
+ char c_rdevminor[8];
+ char c_namesize[8];
+ char c_check[8];
+};
+
+static struct cpio_hdr_field cpio_newc_header_info[] __initdata = {
+ NEWC_FIELD(c_ino, &ino),
+ NEWC_FIELD(c_mode, &mode_u32),
+ NEWC_FIELD(c_uid, &uid),
+ NEWC_FIELD(c_gid, &gid),
+ NEWC_FIELD(c_nlink, &nlink),
+ NEWC_FIELD(c_mtime, &mtime),
+ NEWC_FIELD(c_filesize, &body_len),
+ NEWC_FIELD(c_devmajor, &major),
+ NEWC_FIELD(c_devminor, &minor),
+ NEWC_FIELD(c_rdevmajor, &rmajor),
+ NEWC_FIELD(c_rdevminor, &rminor),
+ NEWC_FIELD(c_namesize, &name_len),
+ { 0 },
+};
static void __init parse_header(char *s)
{
- unsigned long parsed[12];
- char buf[9];
- int i;
-
- buf[8] = '\0';
- for (i = 0; i < 12; i++, s += 8) {
- memcpy(buf, s, 8);
- parsed[i] = simple_strtoul(buf, NULL, 16);
+ char buf[CPIO_MAX_FIELD_SIZE + 1];
+ struct cpio_hdr_field *field = cpio_newc_header_info;
+
+ while (field->size) {
+ int ret = 0;
+
+ memcpy(buf, s + field->offset, field->size);
+ buf[field->size] = '\0';
+ switch (field->out_size) {
+ case sizeof(u32):
+ ret = kstrtou32(buf, 16, field->out);
+ pr_debug("cpio field %s: %u, buf: %s\n",
+ field->name, *(u32 *)field->out, buf);
+ break;
+ case sizeof(u64):
+ ret = kstrtou64(buf, 16, field->out);
+ pr_debug("cpio field %s: %llu, buf: %s\n",
+ field->name, *(u64 *)field->out, buf);
+ break;
+ default:
+ BUG_ON(1);
+ }
+
+ if (ret)
+ pr_err("invalid cpio header field (%d)", ret);
+ field++;
}
- 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];
+ mode = mode_u32;
}
/* FSM */
@@ -234,7 +295,7 @@ static __initdata char *header_buf, *symlink_buf, *name_buf;
static int __init do_start(void)
{
- read_into(header_buf, 6, do_format);
+ read_into(header_buf, CPIO_MAGIC_SIZE, do_format);
return 0;
}
@@ -254,15 +315,15 @@ static int __init do_collect(void)
static int __init do_format(void)
{
- if (memcmp(collected, "070707", 6)==0) {
+ if (memcmp(collected, "070707", CPIO_MAGIC_SIZE) == 0) {
error("incorrect cpio method used: use -H newc option");
return 1;
}
- if (memcmp(collected, "070701", 6)) {
+ if (memcmp(collected, "070701", CPIO_MAGIC_SIZE)) {
error("no cpio magic");
return 1;
}
- read_into(header_buf, 104, do_header);
+ read_into(header_buf, sizeof(struct cpio_newc_header), do_header);
return 0;
}
@@ -374,6 +435,7 @@ static int __init do_create(void)
} else if (S_ISBLK(mode) || S_ISCHR(mode) ||
S_ISFIFO(mode) || S_ISSOCK(mode)) {
if (maybe_link(name_buf) == 0) {
+ u32 rdev = new_encode_dev(MKDEV(rmajor, rminor));
sys_mknod(name_buf, mode, rdev);
sys_chown(name_buf, uid, gid);
sys_chmod(name_buf, mode);
@@ -464,7 +526,7 @@ static char * __init unpack_to_rootfs(char *buf, unsigned long len)
const char *compress_name;
static __initdata char msg_buf[64];
- header_buf = kmalloc(104, GFP_KERNEL);
+ header_buf = kmalloc(CPIO_MAX_HEADER_SIZE, GFP_KERNEL);
symlink_buf = kmalloc(PATH_MAX + 1, GFP_KERNEL);
name_buf = kmalloc(N_ALIGN(PATH_MAX), GFP_KERNEL);
--
2.10.3.dirty
On Thu, Jan 25, 2018 at 4:27 AM, Taras Kondratiuk <[email protected]> wrote:
> Many of the Linux security/integrity features are dependent on file
> metadata, stored as extended attributes (xattrs), for making decisions.
> These features need to be initialized during initcall and enabled as
> early as possible for complete security coverage.
>
> Initramfs (tmpfs) supports xattrs, but newc CPIO archive format does not
> support including them into the archive.
>
> This patch describes "extended" newc format (newcx) that is based on
> newc and has following changes:
> - extended attributes support
> - increased size of filesize to support files >4GB.
> - increased mtime field size to have usec precision and more than
> 32-bit of seconds.
> - removed unused checksum field.
>
> Signed-off-by: Taras Kondratiuk <[email protected]>
> Signed-off-by: Mimi Zohar <[email protected]>
> Signed-off-by: Victor Kamensky <[email protected]>
Ah nice, I like the extension of the time handling, that certainly
addresses one of the issues with y2038 that we have previously
hacked around in an ugly way (interpreting the 32-bit
number as unsigned).
However, if this is to become a generally supported format
for cpio files, could we make it use nanosecond resolution
instead? The issue that I see with microseconds is that
storing a file in an archive and extracting it again would
otherwise keep the mtime stamp /almost/ identical on file
systems that have nanosecond resolution, but most of
the time a comparison would indicate that the files are
not the same.
Unfortunately, the range of a 64-bit nanoseconds counter
is still a bit limited (584 years, or half of that if we make it
signed). While this is clearly enough for the uses in
initramfs, it still has a similar problem: someone creating
a fake timestamp a long time in the past or future on
a file system would lose information after going though
cpio.
Arnd
Quoting Arnd Bergmann (2018-01-25 01:29:12)
> On Thu, Jan 25, 2018 at 4:27 AM, Taras Kondratiuk <[email protected]> wrote:
> > Many of the Linux security/integrity features are dependent on file
> > metadata, stored as extended attributes (xattrs), for making decisions.
> > These features need to be initialized during initcall and enabled as
> > early as possible for complete security coverage.
> >
> > Initramfs (tmpfs) supports xattrs, but newc CPIO archive format does not
> > support including them into the archive.
> >
> > This patch describes "extended" newc format (newcx) that is based on
> > newc and has following changes:
> > - extended attributes support
> > - increased size of filesize to support files >4GB.
> > - increased mtime field size to have usec precision and more than
> > 32-bit of seconds.
> > - removed unused checksum field.
> >
> > Signed-off-by: Taras Kondratiuk <[email protected]>
> > Signed-off-by: Mimi Zohar <[email protected]>
> > Signed-off-by: Victor Kamensky <[email protected]>
>
> Ah nice, I like the extension of the time handling, that certainly
> addresses one of the issues with y2038 that we have previously
> hacked around in an ugly way (interpreting the 32-bit
> number as unsigned).
>
> However, if this is to become a generally supported format
> for cpio files, could we make it use nanosecond resolution
> instead? The issue that I see with microseconds is that
> storing a file in an archive and extracting it again would
> otherwise keep the mtime stamp /almost/ identical on file
> systems that have nanosecond resolution, but most of
> the time a comparison would indicate that the files are
> not the same.
>
> Unfortunately, the range of a 64-bit nanoseconds counter
> is still a bit limited (584 years, or half of that if we make it
> signed). While this is clearly enough for the uses in
> initramfs, it still has a similar problem: someone creating
> a fake timestamp a long time in the past or future on
> a file system would lose information after going though
> cpio.
We can match statx(2) by having 64 bits for seconds plus 32 bits for
nanoseconds. For initramfs nanoseconds field can be ignored during
unpacking.
On Thu, Jan 25, 2018 at 9:26 PM, Taras Kondratiuk <[email protected]> wrote:
> Quoting Arnd Bergmann (2018-01-25 01:29:12)
>> On Thu, Jan 25, 2018 at 4:27 AM, Taras Kondratiuk <[email protected]> wrote:
>
> We can match statx(2) by having 64 bits for seconds plus 32 bits for
> nanoseconds.
Ok.
> For initramfs nanoseconds field can be ignored during
> unpacking.
That sounds like a pointless microoptimization. Most likely we won't ever
need the nanoseconds in the initramfs, but it's trivial to just copy them
into the right field, and not adding that one source line would probably
involve adding a one-line source comment to explain the omission ;-)
Arnd
Quoting Arnd Bergmann (2018-01-25 13:02:49)
> On Thu, Jan 25, 2018 at 9:26 PM, Taras Kondratiuk <[email protected]> wrote:
>
> > For initramfs nanoseconds field can be ignored during
> > unpacking.
>
> That sounds like a pointless microoptimization. Most likely we won't ever
> need the nanoseconds in the initramfs, but it's trivial to just copy them
> into the right field, and not adding that one source line would probably
> involve adding a one-line source comment to explain the omission ;-)
Agree.
On 01/24/2018 09:27 PM, Taras Kondratiuk wrote:
> diff --git a/usr/gen_init_cpio.c b/usr/gen_init_cpio.c
> index 7a2a6d85345d..78a47a5bdcb1 100644
> --- a/usr/gen_init_cpio.c
> +++ b/usr/gen_init_cpio.c
> @@ -10,6 +10,7 @@
> #include <errno.h>
> #include <ctype.h>
> #include <limits.h>
> +#include <assert.h>
You're adding an assert? Really?
> fputs(s, stdout);
> - offset += 110;
> + assert((offset & 3) == 0);
> + offset += cpio_hdr_size;
Why?
Rob
On 01/25/2018 03:29 AM, Arnd Bergmann wrote:
> On Thu, Jan 25, 2018 at 4:27 AM, Taras Kondratiuk <[email protected]> wrote:
>> Many of the Linux security/integrity features are dependent on file
>> metadata, stored as extended attributes (xattrs), for making decisions.
>> These features need to be initialized during initcall and enabled as
>> early as possible for complete security coverage.
>>
>> Initramfs (tmpfs) supports xattrs, but newc CPIO archive format does not
>> support including them into the archive.
>>
>> This patch describes "extended" newc format (newcx) that is based on
>> newc and has following changes:
>> - extended attributes support
>> - increased size of filesize to support files >4GB.
>> - increased mtime field size to have usec precision and more than
>> 32-bit of seconds.
>> - removed unused checksum field.
>>
>> Signed-off-by: Taras Kondratiuk <[email protected]>
>> Signed-off-by: Mimi Zohar <[email protected]>
>> Signed-off-by: Victor Kamensky <[email protected]>
>
> Ah nice, I like the extension of the time handling, that certainly
> addresses one of the issues with y2038 that we have previously
> hacked around in an ugly way (interpreting the 32-bit
> number as unsigned).
Taras and I exchanged email like a year ago working out format stuff, so
I don't have any real complaints. My feedback's already worked in, and I
can make toybox cpio support -h newcx as soon as the format's finalized
and I get a free weekend.
That said, I don't think -h newcx should emit (or recognize) the
"TRAILER!!!1!" entry. That's kinda silly in-band signaling for 2018:
files have a length, pipes provide EOF, and each cpiox entry starts with
6 bytes of c_magic anyway. (I stopped toybox from producing the TRAILER
entry back in june, toybox commit 32550751997d, and the kernel consumes
the resulting cpio just fine. All the trailer does is prevent you from
concatenating cpio files, which is a feature multiple people asked me for.)
> However, if this is to become a generally supported format
> for cpio files,
After Joerg Schilling dies (or admits solaris has) it might even make it
into posix.
> could we make it use nanosecond resolution
> instead? The issue that I see with microseconds is that
> storing a file in an archive and extracting it again would
> otherwise keep the mtime stamp /almost/ identical on file
> systems that have nanosecond resolution, but most of
> the time a comparison would indicate that the files are
> not the same.
I have no strong opinion on this? The tmpfs is still going to track
nanoseconds, this is just rounding when it populates them.
> Unfortunately, the range of a 64-bit nanoseconds counter
> is still a bit limited (584 years, or half of that if we make it
> signed). While this is clearly enough for the uses in
> initramfs, it still has a similar problem: someone creating
> a fake timestamp a long time in the past or future on
> a file system would lose information after going though
> cpio.
Hence microseconds. This came up in email when we were talking about
this (like a year ago) and I decided I didn't care. :)
64 bits of microseconds is +- 584 centuries, while being accurate
enough[1] that making a getpid() syscall probably takes longer than that
on our highest end boxen, let alone doing a dentry lookup in the vfs
(even if it's hot in cache).
Rob
[1] Is future proofing an issue here? The s-curve of moore's law started
bending down around y2k back when Intel had to recall its 1.13ghz
pentium III for having overclocked its own chip at the factory, and it's
pretty darn flat these days. Clock speeds first hit 4ghz 15 years ago
and haven't been back, most of the work since 2005 has been about
parallelism, and recent performance improvements are once again going to
pentium 4 pipeline length levels of absurdity, as meltdown/spectre
demonstrates (140 instructions of prefetch!??!?). Maybe intel will make
9 nanometer manufacturing work, but atomic limits are already an issue.
The problem with 1 second timestamps was you honestly could confuse
"make" about which file was newer once an exec() could complete in the
same second having done real work. That was the motivating issue causing
the change, going to nanoseconds was just the big hammer of "this is
large enough it won't matter again in our lifetimes". But nanosecond
time stamps are recording more jitter than useful information, and that
seems unlikely to change this century?
On 01/24/2018 09:27 PM, Taras Kondratiuk wrote:
> diff --git a/Documentation/early-userspace/buffer-format.txt b/Documentation/early-userspace/buffer-format.txt
> index e1fd7f9dad16..d818df4f72dc 100644
> --- a/Documentation/early-userspace/buffer-format.txt
> +++ b/Documentation/early-userspace/buffer-format.txt
> +compressed and/or uncompressed cpio archives; arbitrary amounts
> +zero bytes (for padding) can be added between members.
Missing "of" between amounts and zero. (Yeah it was in the original, but
if you're touching it anyway...)
> +c_xattrs_size 8 bytes Size of xattrs field
> +
> +Most of the fields match cpio_newc_header except c_mtime that contains
> +microseconds. c_chksum field is dropped.
> +
> +xattr_size is a total size of xattr_entry including 8 bytes of
> +xattr_size. xattr_size has the same hexadecimal ASCII encoding as other
> +fields of cpio header.
xattrs_size or xattr_size?
Total nitpicks, I know. :)
Rob
On Fri, Jan 26, 2018 at 3:39 AM, Rob Landley <[email protected]> wrote:
> The problem with 1 second timestamps was you honestly could confuse
> "make" about which file was newer once an exec() could complete in the
> same second having done real work. That was the motivating issue causing
> the change, going to nanoseconds was just the big hammer of "this is
> large enough it won't matter again in our lifetimes". But nanosecond
> time stamps are recording more jitter than useful information, and that
> seems unlikely to change this century?
Sure, the only thing we really need the nanosecond timestamp for is
to keep them identical. E.g. if you use cpio to make an exact copy
of a file system, using microseconds timestamps will round all mtime
values. If you then use 'rsync' to compare/update the two copies
without passing a --modify-window= or --size-only, it will have
to read all files in rather then skipping those with identical size and
mtime.
Side note: the default behavior for file systems is actually to only use
the coarse timestamps of the last timer tick, so you actually do get
identical timestamps in practice, plus six digits of nonsense:
(on tmpfs)
$ for i in {000..999} ; do > $i ; done; stat --format="%y" * | uniq -c
86 2018-01-26 10:01:48.811135084 +0100
469 2018-01-26 10:01:48.815135143 +0100
445 2018-01-26 10:01:48.819135201 +0100
Arnd
On Thu, 25 Jan 2018, Rob Landley wrote:
> That said, I don't think -h newcx should emit (or recognize) the
> "TRAILER!!!1!" entry. That's kinda silly in-band signaling for 2018:
> files have a length, pipes provide EOF, and each cpiox entry starts with
> 6 bytes of c_magic anyway. (I stopped toybox from producing the TRAILER
> entry back in june, toybox commit 32550751997d, and the kernel consumes
> the resulting cpio just fine. All the trailer does is prevent you from
> concatenating cpio files, which is a feature multiple people asked me for.)
Not in the kernel. What TRAILER does in the kernel is to act as a
barrier for the hardlink creation state, which IS a good thing. You
could just specify it as such for "newcx".
The kernel will continue reading for more entries after TRAILER, so
concatenation is not broken by TRAILER. It is also insensitive to
NUL-padding length (as long as it is 4-byte aligned), which is another
nice feature you could specify for "newcx".
Also, the kernel does something nothing in userspace ever tried to,
AFAIK: it detects compression signatures along with the CPIO header
signatures, and thus it can take several compressed and uncompressed
archives concatenater together (and the compressor doesn't need to be
the same, either).
--
Henrique Holschuh
On Fri, 26 Jan 2018, Henrique de Moraes Holschuh wrote:
> On Thu, 25 Jan 2018, Rob Landley wrote:
>> That said, I don't think -h newcx should emit (or recognize) the
>> "TRAILER!!!1!" entry. That's kinda silly in-band signaling for 2018:
>> files have a length, pipes provide EOF, and each cpiox entry starts with
>> 6 bytes of c_magic anyway.
My understanding that TRAILER is really used on tape devices,
there is no notion of file end in this case, it is just a stream of bytes
from char device.
Thanks,
Victor
>> (I stopped toybox from producing the TRAILER
>> entry back in june, toybox commit 32550751997d, and the kernel consumes
>> the resulting cpio just fine. All the trailer does is prevent you from
>> concatenating cpio files, which is a feature multiple people asked me for.)
>
> Not in the kernel. What TRAILER does in the kernel is to act as a
> barrier for the hardlink creation state, which IS a good thing. You
> could just specify it as such for "newcx".
>
> The kernel will continue reading for more entries after TRAILER, so
> concatenation is not broken by TRAILER. It is also insensitive to
> NUL-padding length (as long as it is 4-byte aligned), which is another
> nice feature you could specify for "newcx".
>
> Also, the kernel does something nothing in userspace ever tried to,
> AFAIK: it detects compression signatures along with the CPIO header
> signatures, and thus it can take several compressed and uncompressed
> archives concatenater together (and the compressor doesn't need to be
> the same, either).
> --
> Henrique Holschuh
>
On Fri, 26 Jan 2018, Victor Kamensky wrote:
> On Fri, 26 Jan 2018, Henrique de Moraes Holschuh wrote:
> > On Thu, 25 Jan 2018, Rob Landley wrote:
> > > That said, I don't think -h newcx should emit (or recognize) the
> > > "TRAILER!!!1!" entry. That's kinda silly in-band signaling for 2018:
> > > files have a length, pipes provide EOF, and each cpiox entry starts with
> > > 6 bytes of c_magic anyway.
>
> My understanding that TRAILER is really used on tape devices,
> there is no notion of file end in this case, it is just a stream of bytes
> from char device.
TRAILER is really used anywhere you can have several cpio archives
concatenated, which is the exact case of a Linux initramfs, not just
tape.
The initramfs format takes *one or more* cpio archives, concatenated.
Each archive may be independently compressed (using whatever supported
compression method), or uncompressed[1]. EOF or size information can
only tell you where the entire concatenated archive ends, not where each
"segment" (independent cpio archive that was concatenated into the
whole) ends.
TRAILER is the only decent way to know the concatenation points.
Knowing where these points are is necessary for the kernel, due to the
way hardlink encoding is done on cpio archives: one has to reset the
state of the hardlink-tracking table between cpio archives that were
concatenated, for safety (and sysadmin sanity) reasons.
[1] for the special case when one includes an "early initramfs" section
for firmware (microcode, etc) updates, the archive(s) containing the
firmware data must be uncompressed, and these archives must come before
compressed archives in the concatenation.
--
Henrique Holschuh
Quoting Rob Landley (2018-01-25 18:40:25)
> On 01/24/2018 09:27 PM, Taras Kondratiuk wrote:
> > diff --git a/usr/gen_init_cpio.c b/usr/gen_init_cpio.c
> > index 7a2a6d85345d..78a47a5bdcb1 100644
> > --- a/usr/gen_init_cpio.c
> > +++ b/usr/gen_init_cpio.c
> > @@ -10,6 +10,7 @@
> > #include <errno.h>
> > #include <ctype.h>
> > #include <limits.h>
> > +#include <assert.h>
>
> You're adding an assert? Really?
>
> > fputs(s, stdout);
> > - offset += 110;
> > + assert((offset & 3) == 0);
> > + offset += cpio_hdr_size;
>
> Why?
Current code assumes that previous file entry in cpio is padded to 4
byte boundary. The assert just captures this assumption. It should be
true unless there is a bug in the code and some padding is missing.
Actually this assert should be a part of the next patch (#12) that
modifies padding code.
Quoting Rob Landley (2018-01-25 18:40:54)
> On 01/24/2018 09:27 PM, Taras Kondratiuk wrote:
> > diff --git a/Documentation/early-userspace/buffer-format.txt b/Documentation/early-userspace/buffer-format.txt
> > index e1fd7f9dad16..d818df4f72dc 100644
> > --- a/Documentation/early-userspace/buffer-format.txt
> > +++ b/Documentation/early-userspace/buffer-format.txt
>
> > +compressed and/or uncompressed cpio archives; arbitrary amounts
> > +zero bytes (for padding) can be added between members.
>
> Missing "of" between amounts and zero. (Yeah it was in the original, but
> if you're touching it anyway...)
>
> > +c_xattrs_size 8 bytes Size of xattrs field
> > +
> > +Most of the fields match cpio_newc_header except c_mtime that contains
> > +microseconds. c_chksum field is dropped.
> > +
> > +xattr_size is a total size of xattr_entry including 8 bytes of
> > +xattr_size. xattr_size has the same hexadecimal ASCII encoding as other
> > +fields of cpio header.
>
> xattrs_size or xattr_size?
>
> Total nitpicks, I know. :)
xattr_size here refers to size of each xattr_entry:
xattr_entry := xattr_size[8] + xattr_name + "\0" + xattr_value
I'll move this paragraph closer to that line.