Re: [RFC][PATCH] fs: set xattrs in initramfs from regular files

From: Casey Schaufler
Date: Fri Nov 23 2018 - 14:04:16 EST


On 11/22/2018 7:49 AM, Roberto Sassu wrote:
> Although rootfs (tmpfs) supports xattrs, they are not set due to the
> limitation of the cpio format. A new format called 'newcx' was proposed to
> overcome this limitation.
>
> However, it looks like that adding a new format is not simple: 15 kernel
> patches; user space tools must support the new format; mistakes made in the
> past should be avoided; it is unclear whether the kernel should switch from
> cpio to tar.
>
> The aim of this patch is to provide the same functionality without
> introducing a new format. The value of xattrs is placed in regular files
> having the same file name as the files xattrs are added to, plus a
> separator and the xattr name (<filename>.xattr-<xattr name>).
>
> Example:
>
> '/bin/cat.xattr-security.ima' is the name of a file containing the value of
> the security.ima xattr to be added to /bin/cat.
>
> At kernel initialization time, the kernel iterates over the rootfs
> filesystem, and if it encounters files with the '.xattr-' separator, it
> reads the content and adds the xattr to the file without the suffix.

No.

Really, no.

It would be incredibly easy to use this mechanism to break
into systems.
Â

> This proposal requires that LSMs and IMA allow the read and setxattr
> operations. This should not be a concern since: files with xattr values
> are not parsed by the kernel; user space processes are not yet executed.
>
> It would be possible to include all xattrs in the same file, but this
> increases the risk of the kernel being compromised by parsing the content.

The kernel mustn't do this.

>
> Signed-off-by: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
> ---
> fs/Makefile | 2 +-
> fs/readxattr.c | 171 +++++++++++++++++++++++++++++++++++++++++++++
> include/linux/fs.h | 2 +
> init/main.c | 1 +
> 4 files changed, 175 insertions(+), 1 deletion(-)
> create mode 100644 fs/readxattr.c
>
> diff --git a/fs/Makefile b/fs/Makefile
> index 293733f61594..738e1a4e4aff 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -12,7 +12,7 @@ obj-y := open.o read_write.o file_table.o super.o \
> attr.o bad_inode.o file.o filesystems.o namespace.o \
> seq_file.o xattr.o libfs.o fs-writeback.o \
> pnode.o splice.o sync.o utimes.o d_path.o \
> - stack.o fs_struct.o statfs.o fs_pin.o nsfs.o
> + stack.o fs_struct.o statfs.o fs_pin.o nsfs.o readxattr.o
>
> ifeq ($(CONFIG_BLOCK),y)
> obj-y += buffer.o block_dev.o direct-io.o mpage.o
> diff --git a/fs/readxattr.c b/fs/readxattr.c
> new file mode 100644
> index 000000000000..01838f6f1e92
> --- /dev/null
> +++ b/fs/readxattr.c
> @@ -0,0 +1,171 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2018 Huawei Technologies Duesseldorf GmbH
> + *
> + * Author: Roberto Sassu <roberto.sassu@xxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.
> + *
> + * File: readxattr.c
> + * Read extended attributes from regular files in the initial ram disk
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/export.h>
> +#include <linux/xattr.h>
> +#include <linux/file.h>
> +#include <linux/cred.h>
> +#include <linux/namei.h>
> +#include <linux/fs.h>
> +
> +#include "internal.h"
> +
> +#define SETXATTR_FILENAME ".setxattr"
> +#define FILENAME_XATTR_SEP ".xattr-"
> +
> +
> +struct readdir_callback {
> + struct dir_context ctx;
> + struct path *path;
> +};
> +
> +LIST_HEAD(dir_list);
> +
> +struct dir_path {
> + struct list_head next;
> + struct path path;
> +};
> +
> +static int __init read_set_xattr(struct dir_context *__ctx, const char *name,
> + int namelen, loff_t offset, u64 ino,
> + unsigned int d_type)
> +{
> + struct readdir_callback *ctx = container_of(__ctx, typeof(*ctx), ctx);
> + struct path *dir = ctx->path, source_path, target_path;
> + char filename[NAME_MAX + 1], *xattrname, *separator;
> + struct dir_path *subdir;
> + struct file *file;
> + void *datap;
> + loff_t size;
> + int result;
> +
> + if (!strcmp(name, ".") || !strcmp(name, ".."))
> + return 0;
> +
> + result = vfs_path_lookup(dir->dentry, dir->mnt, name, 0, &source_path);
> + if (result)
> + return 0;
> +
> + size = i_size_read(source_path.dentry->d_inode);
> + if (size > XATTR_SIZE_MAX)
> + goto out;
> +
> + if (source_path.dentry->d_inode->i_sb != dir->dentry->d_inode->i_sb)
> + goto out;
> +
> + if (!S_ISREG(source_path.dentry->d_inode->i_mode) &&
> + !S_ISDIR(source_path.dentry->d_inode->i_mode))
> + goto out;
> +
> + if (S_ISREG(source_path.dentry->d_inode->i_mode)) {
> + separator = strstr(name, FILENAME_XATTR_SEP);
> + if (!separator)
> + goto out;
> +
> + xattrname = separator + sizeof(FILENAME_XATTR_SEP) - 1;
> + if (strlen(xattrname) > XATTR_NAME_MAX)
> + goto out;
> + } else {
> + subdir = kmalloc(sizeof(*subdir), GFP_KERNEL);
> + if (subdir) {
> + subdir->path.dentry = source_path.dentry;
> + subdir->path.mnt = source_path.mnt;
> +
> + list_add(&subdir->next, &dir_list);
> + }
> +
> + return 0;
> + }
> +
> + file = dentry_open(&source_path, O_RDONLY, current_cred());
> + if (IS_ERR(file))
> + goto out;
> +
> + result = kernel_read_file(file, &datap, &size, 0, READING_XATTR);
> + if (result)
> + goto out_fput;
> +
> + if (separator != name) {
> + snprintf(filename, sizeof(filename), "%.*s",
> + (int)(namelen - strlen(separator)), name);
> +
> + result = vfs_path_lookup(dir->dentry, dir->mnt, filename, 0,
> + &target_path);
> + if (result)
> + goto out_vfree;
> +
> + inode_lock(target_path.dentry->d_inode);
> + } else {
> + target_path.dentry = dir->dentry;
> + target_path.mnt = dir->mnt;
> + }
> +
> + __vfs_setxattr_noperm(target_path.dentry, xattrname, datap, size, 0);
> +
> + if (separator != name) {
> + inode_unlock(target_path.dentry->d_inode);
> + path_put(&target_path);
> + }
> +out_vfree:
> + vfree(datap);
> +out_fput:
> + fput(file);
> +out:
> + path_put(&source_path);
> + return 0;
> +}
> +
> +void __init set_xattrs_initrd(void)
> +{
> + struct readdir_callback buf = {
> + .ctx.actor = read_set_xattr,
> + };
> +
> + struct dir_path dir, *cur_dir;
> + struct path path;
> + struct file *file;
> + int result;
> +
> + result = kern_path(SETXATTR_FILENAME, 0, &path);
> + if (result)
> + return;
> +
> + path_put(&path);
> +
> + result = kern_path("/", 0, &dir.path);
> + if (result)
> + return;
> +
> + list_add(&dir.next, &dir_list);
> +
> + while (!list_empty(&dir_list)) {
> + cur_dir = list_first_entry(&dir_list, typeof(*cur_dir), next);
> +
> + file = dentry_open(&cur_dir->path, O_RDONLY, current_cred());
> + if (file) {
> + buf.path = &cur_dir->path;
> + iterate_dir(file, &buf.ctx);
> + fput(file);
> + }
> +
> + path_put(&cur_dir->path);
> + list_del(&cur_dir->next);
> +
> + if (cur_dir != &dir)
> + kfree(cur_dir);
> + }
> +}
> +EXPORT_SYMBOL_GPL(set_xattrs_initrd);
> diff --git a/include/linux/fs.h b/include/linux/fs.h
> index c95c0807471f..b04edc1c32e9 100644
> --- a/include/linux/fs.h
> +++ b/include/linux/fs.h
> @@ -2894,6 +2894,7 @@ extern int do_pipe_flags(int *, int);
> id(KEXEC_INITRAMFS, kexec-initramfs) \
> id(POLICY, security-policy) \
> id(X509_CERTIFICATE, x509-certificate) \
> + id(XATTR, xattr) \
> id(MAX_ID, )
>
> #define __fid_enumify(ENUM, dummy) READING_ ## ENUM,
> @@ -3156,6 +3157,7 @@ const char *simple_get_link(struct dentry *, struct inode *,
> extern const struct inode_operations simple_symlink_inode_operations;
>
> extern int iterate_dir(struct file *, struct dir_context *);
> +extern void set_xattrs_initrd(void);
>
> extern int vfs_statx(int, const char __user *, int, struct kstat *, u32);
> extern int vfs_statx_fd(unsigned int, struct kstat *, u32, unsigned int);
> diff --git a/init/main.c b/init/main.c
> index ee147103ba1b..a2f63bc8f9d4 100644
> --- a/init/main.c
> +++ b/init/main.c
> @@ -1180,5 +1180,6 @@ static noinline void __init kernel_init_freeable(void)
> */
>
> integrity_load_keys();
> + set_xattrs_initrd();
> load_default_modules();
> }