lkml.org 
[lkml]   [2009]   [May]   [12]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
SubjectRe: [patch 00/13] devtmpfs patches
From
Date
On Tue, 2009-05-12 at 17:54 +0200, Kay Sievers wrote:
> On Tue, May 12, 2009 at 17:35, Stephen Smalley <sds@tycho.nsa.gov> wrote:
>
> > I think the issue is that the devtmpfs functions are calling vfs helpers
> > to create and unlink the device nodes, and those helpers apply
> > permission checks based on the current process' credentials. I think a
> > similar issue arose in sysfs a while ago. Options are to either bypass
> > the vfs helpers to avoid that permission checking for what I think are
> > intended to be kernel-internal operations, or to override credentials
> > temporarily around the calls to the vfs helpers, ala:
> > new_cred = prepare_kernel_cred(NULL);
> > old_cred = override_creds(new_cred);
> > rc = vfs_mknod(...);
> > revert_creds(old_cred);
>
> Ah, I see.

Here is the current state of the patch. It would be great, if you can
have a quick look, if that matches what you meant. It runs fine here,
but I didn't try any security enforcing software so far, which might run
into trouble without the credential stuff.

Thanks a lot,
Kay


From: Kay Sievers <kay.sievers@vrfy.org>
Subject: Driver Core: devtmpfs - kernel-maintained tmpfs-based /dev

Devtmpfs lets the kernel create a tmpfs instance called devtmpfs
very early at kernel initialization, before any driver-core device
is registered. Every device with a major/minor will create a
device node in devtmpfs.

Devtmpfs can be changed and altered by userspace at any time,
and in any way needed - just like today's udev-mounted tmpfs. Unmodified
udev versions will run just fine on top of it, and will recognize an
already existing kernel-created device node and use it.

The default node permissions are root:root 0600. Proper permissions
and user/group ownership, meaningful symlinks, all other policy besides
the node name, still needs to be applied by udev, just as without
devtmpfs.

If a node is created by devtmps, devtmpfs will remove the device node
when the device goes away. If the device node was created by
userspace, or the devtmpfs created node was replaced by userspace, it
will not be removed by devtmpfs.

If automatically mounted, it makes init=/bin/sh work without any
further userspace support. /dev will be fully populated and dynamic,
and always reflect the current device state of the kernel. Especially
in the face of the already implemented dynamic device numbers, this
can be very helpful in a rescue situation, where static devices nodes
may no longer work correctly.
Custom, embedded-like systems should be able to use this as a dynamic
/dev directory without any need for aditional userspace tools.

With the kernel populated /dev, existing initramfs or kernel-mount
bootup logic can be optimized to be more efficient, and not to require a
full coldplug run, which is currently needed to bootstrap the inital
/dev directory content, before continuing bringing up the rest of
the system. There will be no missed events to replay, because /dev is
available before the first kernel device is registered with the
driver-core.

Signed-off-by: Kay Sievers <kay.sievers@vrfy.org>
Signed-off-by: Jan Blunck <jblunck@suse.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

---
drivers/base/Kconfig | 25 ++
drivers/base/Makefile | 1
drivers/base/base.h | 6
drivers/base/core.c | 3
drivers/base/devtmpfs.c | 405 +++++++++++++++++++++++++++++++++++++++++++++++
drivers/base/init.c | 1
include/linux/device.h | 10 +
include/linux/shmem_fs.h | 2
init/do_mounts.c | 2
init/main.c | 2
mm/shmem.c | 6
11 files changed, 458 insertions(+), 5 deletions(-)

--- a/drivers/base/Kconfig
+++ b/drivers/base/Kconfig
@@ -8,6 +8,31 @@ config UEVENT_HELPER_PATH
Path to uevent helper program forked by the kernel for
every uevent.

+config DEVTMPFS
+ bool "Create a kernel maintained /dev tmpfs (EXPERIMENTAL)"
+ depends on HOTPLUG
+ help
+ This creates a tmpfs filesystem, and mounts it at bootup
+ and mounts it at /dev. The kernel driver core creates device
+ nodes for all registered devices in that filesystem. All device
+ nodes are owned by root and have the default mode of 0600.
+ Userspace can add and delete the nodes as needed. This is
+ intended to simplify bootup, and make it possible to delay
+ the initial coldplug at bootup done by udev in userspace.
+ It should also provide a simpler way for rescue systems
+ to bring up a kernel with dynamic major/minor numbers.
+ Meaningful symlinks, permissions and device ownership must
+ still be handled by userspace.
+ If unsure, say N here.
+
+config DEVTMPFS_MOUNT
+ bool "Automount devtmpfs at /dev"
+ depends on DEVTMPFS
+ help
+ This will mount devtmpfs at /dev if the kernel mounts the root
+ filesystem. It will not affect initramfs based mounting.
+ If unsure, say N here.
+
config STANDALONE
bool "Select only drivers that don't need compile-time external firmware" if EXPERIMENTAL
default y
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -4,6 +4,7 @@ obj-y := core.o sys.o bus.o dd.o \
driver.o class.o platform.o \
cpu.o firmware.o init.o map.o devres.o \
attribute_container.o transport_class.o
+obj-$(CONFIG_DEVTMPFS) += devtmpfs.o
obj-y += power/
obj-$(CONFIG_HAS_DMA) += dma-mapping.o
obj-$(CONFIG_ISA) += isa.o
--- a/drivers/base/base.h
+++ b/drivers/base/base.h
@@ -134,3 +134,9 @@ static inline void module_add_driver(str
struct device_driver *drv) { }
static inline void module_remove_driver(struct device_driver *drv) { }
#endif
+
+#ifdef CONFIG_DEVTMPFS
+extern int devtmpfs_init(void);
+#else
+static inline int devtmpfs_init(void) { return 0; }
+#endif
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -920,6 +920,8 @@ int device_add(struct device *dev)
error = device_create_sys_dev_entry(dev);
if (error)
goto devtattrError;
+
+ devtmpfs_create_node(dev);
}

error = device_add_class_symlinks(dev);
@@ -1063,6 +1065,7 @@ void device_del(struct device *dev)
if (parent)
klist_del(&dev->p->knode_parent);
if (MAJOR(dev->devt)) {
+ devtmpfs_delete_node(dev);
device_remove_sys_dev_entry(dev);
device_remove_file(dev, &devt_attr);
}
--- /dev/null
+++ b/drivers/base/devtmpfs.c
@@ -0,0 +1,405 @@
+/*
+ * devtmpfs - tmpfs based device node filesystem
+ *
+ * Copyright (C) 2009, Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * During bootup, before any driver core device is registered, a tmpfs
+ * filesystem is created. Every device which requests a devno, will
+ * create a device node in this filesystem. The node is named after the
+ * the nameof the device, or the susbsytem can provide a custom name
+ * for the node.
+ *
+ * All devices are owned by root. This is intended to simplify bootup, and
+ * make it possible to delay the initial coldplug done by udev in userspace.
+ *
+ * It should also provide a simpler way for rescue systems to bring up a
+ * kernel with dynamic major/minor numbers.
+ */
+
+#include <linux/kernel.h>
+#include <linux/syscalls.h>
+#include <linux/mount.h>
+#include <linux/device.h>
+#include <linux/genhd.h>
+#include <linux/namei.h>
+#include <linux/fs.h>
+#include <linux/cred.h>
+
+static DECLARE_MUTEX(dev_sb_sem);
+static struct vfsmount *dev_mnt;
+static struct file_system_type dev_fs_type;
+static struct cred *kern_cred;
+
+#if defined CONFIG_DEVTMPFS_MOUNT
+static int dev_mount = 1;
+#else
+static int dev_mount;
+#endif
+
+static int __init mount_param(char *str)
+{
+ dev_mount = simple_strtoul(str, NULL, 0);
+ return 1;
+}
+__setup("devtmpfs.mount=", mount_param);
+
+static int dev_get_sb(struct file_system_type *fs_type, int flags,
+ const char *dev_name, void *data, struct vfsmount *mnt)
+{
+ int ret = 0;
+
+ down(&dev_sb_sem);
+ if (!dev_mnt) {
+ static struct file_system_type *tmpfs;
+
+ tmpfs = get_fs_type("tmpfs");
+ if (!tmpfs) {
+ printk(KERN_ERR "dev_get_sb: unable get tmpfs "
+ "fstype\n");
+ ret = -ENOENT;
+ goto out;
+ }
+ ret = tmpfs->get_sb(fs_type, flags, dev_name, data, mnt);
+ if (ret) {
+ printk(KERN_ERR "dev_get_sb: unable to init "
+ "tmpfs superblock\n");
+ goto out;
+ }
+ dev_mnt = mnt;
+ } else {
+ /* we use a tmpfs superblock, fill_super() is not called */
+ get_sb_single(fs_type, flags, data, NULL, mnt);
+ }
+out:
+ up(&dev_sb_sem);
+ return ret;
+}
+
+static struct file_system_type dev_fs_type = {
+ .name = "devtmpfs",
+ .get_sb = dev_get_sb,
+ .kill_sb = kill_litter_super,
+};
+
+#ifdef CONFIG_BLOCK
+static inline int is_blockdev(struct device *dev)
+{
+ return dev->class == &block_class;
+}
+#else
+static inline int is_blockdev(struct device *dev) { return 0; }
+#endif
+
+static int dev_mkdir(const char *name, mode_t mode)
+{
+ struct nameidata nd;
+ struct dentry *dentry;
+ int err;
+
+ err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,
+ name, LOOKUP_PARENT, &nd);
+ if (err)
+ return err;
+
+ dentry = lookup_create(&nd, 1);
+ if (!IS_ERR(dentry)) {
+ err = vfs_mkdir(nd.path.dentry->d_inode, dentry, mode);
+ dput(dentry);
+ } else {
+ err = PTR_ERR(dentry);
+ }
+ mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+
+ path_put(&nd.path);
+ return err;
+}
+
+static int create_path(const char *nodepath)
+{
+ char *path;
+ struct nameidata nd;
+ int err = 0;
+
+ path = kstrdup(nodepath, GFP_KERNEL);
+ if (!path)
+ return -ENOMEM;
+
+ err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,
+ path, LOOKUP_PARENT, &nd);
+ if (err == 0) {
+ struct dentry *dentry;
+
+ /* create directory right away */
+ dentry = lookup_create(&nd, 1);
+ if (!IS_ERR(dentry)) {
+ err = vfs_mkdir(nd.path.dentry->d_inode,
+ dentry, 0775);
+ dput(dentry);
+ }
+ mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+
+ path_put(&nd.path);
+ } else if (err == -ENOENT) {
+ char *s;
+
+ /* parent directories do not exist, create them */
+ s = path;
+ while (1) {
+ s = strchr(s, '/');
+ if (!s)
+ break;
+ s[0] = '\0';
+ err = dev_mkdir(path, 0755);
+ if (err && err != -EEXIST)
+ break;
+ s[0] = '/';
+ s++;
+ }
+ }
+
+ kfree(path);
+ return err;
+}
+
+int devtmpfs_create_node(struct device *dev)
+{
+ const char *tmp = NULL;
+ const char *nodename;
+ mode_t mode;
+ struct nameidata nd;
+ struct dentry *dentry;
+ int err;
+
+ if (!dev_mnt)
+ return 0;
+
+ nodename = device_get_nodename(dev, &tmp);
+ if (!nodename)
+ return -ENOMEM;
+
+ if (is_blockdev(dev))
+ mode = S_IFBLK|0600;
+ else
+ mode = S_IFCHR|0600;
+
+ err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,
+ nodename, LOOKUP_PARENT, &nd);
+ if (err == -ENOENT) {
+ /* create missing parent directories */
+ create_path(nodename);
+ err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,
+ nodename, LOOKUP_PARENT, &nd);
+ if (err)
+ goto out_name;
+ }
+
+ dentry = lookup_create(&nd, 0);
+ if (!IS_ERR(dentry)) {
+ const struct cred *curr_cred;
+
+ curr_cred = override_creds(kern_cred);
+ err = vfs_mknod(nd.path.dentry->d_inode,
+ dentry, mode, dev->devt);
+ revert_creds(curr_cred);
+ /* mark as kernel created inode */
+ if (!err)
+ dentry->d_inode->i_private = &dev_mnt;
+ dput(dentry);
+ } else {
+ err = PTR_ERR(dentry);
+ }
+ mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+
+ path_put(&nd.path);
+out_name:
+ kfree(tmp);
+ return err;
+}
+
+static int dev_rmdir(const char *name)
+{
+ struct nameidata nd;
+ struct dentry *dentry;
+ int err;
+
+ err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,
+ name, LOOKUP_PARENT, &nd);
+ if (err)
+ return err;
+
+ mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
+ dentry = lookup_one_len(nd.last.name, nd.path.dentry, nd.last.len);
+ if (!IS_ERR(dentry)) {
+ if (dentry->d_inode)
+ err = vfs_rmdir(nd.path.dentry->d_inode, dentry);
+ else
+ err = -ENOENT;
+ dput(dentry);
+ } else {
+ err = PTR_ERR(dentry);
+ }
+ mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+
+ path_put(&nd.path);
+ return err;
+}
+
+static int delete_path(const char *nodepath)
+{
+ const char *path;
+ int err = 0;
+
+ path = kstrdup(nodepath, GFP_KERNEL);
+ if (!path)
+ return -ENOMEM;
+
+ while (1) {
+ char *base;
+
+ base = strrchr(path, '/');
+ if (!base)
+ break;
+ base[0] = '\0';
+ err = dev_rmdir(path);
+ if (err)
+ break;
+ }
+
+ kfree(path);
+ return err;
+}
+
+static int dev_mynode(struct device *dev, struct inode *inode, struct kstat *stat)
+{
+ /* did we create it */
+ if (inode->i_private != &dev_mnt)
+ return 0;
+
+ /* does the dev_t match */
+ if (is_blockdev(dev)) {
+ if (!S_ISBLK(stat->mode))
+ return 0;
+ } else {
+ if (!S_ISCHR(stat->mode))
+ return 0;
+ }
+ if (stat->rdev != dev->devt)
+ return 0;
+
+ /* ours */
+ return 1;
+}
+
+int devtmpfs_delete_node(struct device *dev)
+{
+ const char *tmp = NULL;
+ const char *nodename;
+ struct nameidata nd;
+ struct dentry *dentry;
+ struct kstat stat;
+ int deleted = 1;
+ int err;
+
+ if (!dev_mnt)
+ return 0;
+
+ nodename = device_get_nodename(dev, &tmp);
+ if (!nodename)
+ return -ENOMEM;
+
+ err = vfs_path_lookup(dev_mnt->mnt_root, dev_mnt,
+ nodename, LOOKUP_PARENT, &nd);
+ if (err)
+ goto out_name;
+
+ mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT);
+ dentry = lookup_one_len(nd.last.name, nd.path.dentry, nd.last.len);
+ if (!IS_ERR(dentry)) {
+ if (dentry->d_inode) {
+ err = vfs_getattr(nd.path.mnt, dentry, &stat);
+ if (!err && dev_mynode(dev, dentry->d_inode, &stat)) {
+ err = vfs_unlink(nd.path.dentry->d_inode,
+ dentry);
+ if (!err || err == -ENOENT)
+ deleted = 1;
+ }
+ } else {
+ err = -ENOENT;
+ }
+ dput(dentry);
+ } else {
+ err = PTR_ERR(dentry);
+ }
+ mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
+
+ path_put(&nd.path);
+ if (deleted && strchr(nodename, '/'))
+ delete_path(nodename);
+out_name:
+ kfree(tmp);
+ return err;
+}
+
+/* After the root filesystem is mounted by the kernel at /root, or the
+ * initramfs in extracted at /root, this tmpfs will be mounted at /root/dev.
+ */
+int devtmpfs_mount(const char *mountpoint)
+{
+ struct path path;
+ int err;
+
+ if (!dev_mount)
+ return 0;
+
+ if (!dev_mnt)
+ return 0;
+
+ err = kern_path(mountpoint, LOOKUP_FOLLOW, &path);
+ if (err)
+ return err;
+ err = do_add_mount(dev_mnt, &path, 0, NULL);
+ if (err)
+ printk(KERN_INFO "devtmpfs: error mounting %i\n", err);
+ else
+ printk(KERN_INFO "devtmpfs: mounted\n");
+ path_put(&path);
+ return err;
+}
+
+/*
+ * Create tmpfs mount, created core devices will add their device device
+ * nodes here.
+ */
+int __init devtmpfs_init(void)
+{
+ int err;
+ struct vfsmount *mnt;
+
+ kern_cred = prepare_kernel_cred(NULL);
+ if (!kern_cred) {
+ printk(KERN_ERR "devtmpfs: unable to create credentials\n");
+ return -EINVAL;
+ }
+
+ err = register_filesystem(&dev_fs_type);
+ if (err) {
+ printk(KERN_ERR "devtmpfs: unable to register devtmpfs "
+ "type %i\n", err);
+ put_cred(kern_cred);
+ return err;
+ }
+
+ mnt = kern_mount(&dev_fs_type);
+ if (IS_ERR(mnt)) {
+ err = PTR_ERR(mnt);
+ printk(KERN_ERR "devtmpfs: unable to create devtmpfs %i\n", err);
+ unregister_filesystem(&dev_fs_type);
+ put_cred(kern_cred);
+ return err;
+ }
+
+ dev_mkdir("shm", 01755);
+
+ printk(KERN_INFO "devtmpfs: initialized\n");
+ return 0;
+}
--- a/drivers/base/init.c
+++ b/drivers/base/init.c
@@ -20,6 +20,7 @@
void __init driver_init(void)
{
/* These are the core pieces */
+ devtmpfs_init();
devices_init();
buses_init();
classes_init();
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -553,6 +553,16 @@ extern void put_device(struct device *de

extern void wait_for_device_probe(void);

+#ifdef CONFIG_DEVTMPFS
+extern int devtmpfs_create_node(struct device *dev);
+extern int devtmpfs_delete_node(struct device *dev);
+extern int devtmpfs_mount(const char *mountpoint);
+#else
+static inline int devtmpfs_create_node(struct device *dev) { return 0; }
+static inline int devtmpfs_delete_node(struct device *dev) { return 0; }
+static inline int devtmpfs_mount(const char *mountpoint) { return 0; }
+#endif
+
/* drivers/base/power/shutdown.c */
extern void device_shutdown(void);

--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -42,6 +42,8 @@ static inline struct shmem_inode_info *S
return container_of(inode, struct shmem_inode_info, vfs_inode);
}

+extern int init_tmpfs(void);
+
#ifdef CONFIG_TMPFS_POSIX_ACL
int shmem_permission(struct inode *, int);
int shmem_acl_init(struct inode *, struct inode *);
--- a/init/do_mounts.c
+++ b/init/do_mounts.c
@@ -414,7 +414,7 @@ void __init prepare_namespace(void)

mount_root();
out:
+ devtmpfs_mount("dev");
sys_mount(".", "/", NULL, MS_MOVE, NULL);
sys_chroot(".");
}
-
--- a/init/main.c
+++ b/init/main.c
@@ -64,6 +64,7 @@
#include <linux/idr.h>
#include <linux/ftrace.h>
#include <linux/async.h>
+#include <linux/shmem_fs.h>
#include <trace/boot.h>

#include <asm/io.h>
@@ -778,6 +779,7 @@ static void __init do_basic_setup(void)
init_workqueues();
cpuset_init_smp();
usermodehelper_init();
+ init_tmpfs();
driver_init();
init_irq_proc();
do_initcalls();
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -2523,7 +2523,7 @@ static struct file_system_type tmpfs_fs_
.kill_sb = kill_litter_super,
};

-static int __init init_tmpfs(void)
+int __init init_tmpfs(void)
{
int error;

@@ -2580,7 +2580,7 @@ static struct file_system_type tmpfs_fs_
.kill_sb = kill_litter_super,
};

-static int __init init_tmpfs(void)
+int __init init_tmpfs(void)
{
BUG_ON(register_filesystem(&tmpfs_fs_type) != 0);

@@ -2691,5 +2691,3 @@ int shmem_zero_setup(struct vm_area_stru
vma->vm_ops = &shmem_vm_ops;
return 0;
}
-
-module_init(init_tmpfs)



\
 
 \ /
  Last update: 2009-05-13 00:57    [W:0.192 / U:0.164 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site