[PATCH] Security/sysfs: Enable security xattrs to be set on sysfsfiles, directories, and symlinks.

From: Casey Schaufler
Date: Fri Aug 14 2009 - 01:01:16 EST


From: Casey Schaufler <casey@xxxxxxxxxxxxxxxx>

This patch is in response to David P. Quigley's proposal from
July of this year. That patch provided special case handling of
LSM xattrs in the security name space.

This patch provides an in memory representation of general
xattrs. It currently only allows xattrs in the security namespace,
but that is only because the support of ACLs is beyond the
day's needs. The list of xattrs for a given file is created on
demand and a system that does not use xattrs should be pretty
well oblivious to the changes. On the down side, this requires
an unpleasant locking scheme. Improvements would of course be
welcome.

This scheme should generalize to any memory based file system,
although I have not attempted to create a generic implementation
here.

Signed-off-by: Casey Schaufler <casey@xxxxxxxxxxxxxxxx>

---

fs/sysfs/dir.c | 4
fs/sysfs/inode.c | 210 +++++++++++++++++++++++++++++++++++++++++++
fs/sysfs/symlink.c | 10 +-
fs/sysfs/sysfs.h | 16 +++
4 files changed, 237 insertions(+), 3 deletions(-)

diff -uprN -X linux-2.6/Documentation/dontdiff linux-2.6/fs/sysfs/dir.c linux-0812/fs/sysfs/dir.c
--- linux-2.6/fs/sysfs/dir.c 2009-08-11 16:22:20.000000000 -0700
+++ linux-0812/fs/sysfs/dir.c 2009-08-12 11:10:45.000000000 -0700
@@ -760,6 +760,10 @@ static struct dentry * sysfs_lookup(stru
const struct inode_operations sysfs_dir_inode_operations = {
.lookup = sysfs_lookup,
.setattr = sysfs_setattr,
+ .setxattr = sysfs_setxattr,
+ .getxattr = sysfs_getxattr,
+ .listxattr = sysfs_listxattr,
+ .removexattr = sysfs_removexattr,
};

static void remove_dir(struct sysfs_dirent *sd)
diff -uprN -X linux-2.6/Documentation/dontdiff linux-2.6/fs/sysfs/inode.c linux-0812/fs/sysfs/inode.c
--- linux-2.6/fs/sysfs/inode.c 2009-03-28 13:47:33.000000000 -0700
+++ linux-0812/fs/sysfs/inode.c 2009-08-12 11:08:28.000000000 -0700
@@ -18,6 +18,7 @@
#include <linux/capability.h>
#include <linux/errno.h>
#include <linux/sched.h>
+#include <linux/xattr.h>
#include "sysfs.h"

extern struct super_block * sysfs_sb;
@@ -35,8 +36,13 @@ static struct backing_dev_info sysfs_bac

static const struct inode_operations sysfs_inode_operations ={
.setattr = sysfs_setattr,
+ .setxattr = sysfs_setxattr,
+ .getxattr = sysfs_getxattr,
+ .listxattr = sysfs_listxattr,
+ .removexattr = sysfs_removexattr,
};

+
int __init sysfs_inode_init(void)
{
return bdi_init(&sysfs_backing_dev_info);
@@ -104,6 +110,210 @@ int sysfs_setattr(struct dentry * dentry
return error;
}

+/*
+ * Extended attributes are stored on a list off of the dirent.
+ * The list head itself is allocated when needed so that a file
+ * with no xattrs does not have the overhead of a list head.
+ * Unfortunately, to lock the xattr list for each dentry would
+ * require a lock in each dentry, which would defeat the purpose
+ * of allocating the list head. So one big sysfs xattr lock.
+ *
+ * A better solution would be welcome.
+ */
+static DEFINE_MUTEX(sysfs_xattr_lock);
+
+static struct sysfs_xattr *new_xattr(const char *name, const void *value,
+ size_t size)
+{
+ struct sysfs_xattr *nxattr;
+ void *nvalue;
+ char *nname;
+
+ nxattr = kzalloc(sizeof(*nxattr), GFP_KERNEL);
+ if (!nxattr)
+ return NULL;
+ nvalue = kzalloc(size, GFP_KERNEL);
+ if (!nvalue) {
+ kfree(nxattr);
+ return NULL;
+ }
+ nname = kzalloc(strlen(name) + 1, GFP_KERNEL);
+ if (!nname) {
+ kfree(nxattr);
+ kfree(nvalue);
+ return NULL;
+ }
+ memcpy(nvalue, value, size);
+ strcpy(nname, name);
+ nxattr->sx_name = nname;
+ nxattr->sx_value = nvalue;
+ nxattr->sx_size = size;
+
+ return nxattr;
+}
+
+int sysfs_setxattr(struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ struct sysfs_dirent *sd = dentry->d_fsdata;
+ struct list_head *xlist;
+ struct sysfs_xattr *nxattr;
+ void *nvalue;
+ int rc = 0;
+
+ /*
+ * Only support the security namespace.
+ * Only allow privileged processes to set them.
+ * It has to be OK with the LSM, if any, as well.
+ */
+ if (strncmp(name, XATTR_SECURITY_PREFIX,
+ sizeof XATTR_SECURITY_PREFIX - 1))
+ return -ENOTSUPP;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ mutex_lock(&sysfs_xattr_lock);
+
+ if (!sd->s_xattr) {
+ sd->s_xattr = kzalloc(sizeof(*xlist), GFP_KERNEL);
+ if (!sd->s_xattr) {
+ rc = -ENOMEM;
+ goto unlock_out;
+ }
+ INIT_LIST_HEAD(sd->s_xattr);
+ }
+ xlist = sd->s_xattr;
+
+ list_for_each_entry(nxattr, xlist, list) {
+ if (!strcmp(nxattr->sx_name, name)) {
+ if (flags & XATTR_CREATE) {
+ rc = -EEXIST;
+ goto unlock_out;
+ }
+ nvalue = kzalloc(size, GFP_KERNEL);
+ if (!nvalue) {
+ rc = -ENOMEM;
+ goto unlock_out;
+ }
+ memcpy(nvalue, value, size);
+ kfree(nxattr->sx_value);
+ nxattr->sx_value = nvalue;
+ nxattr->sx_size = size;
+ rc = 0;
+ goto unlock_out;
+ }
+ }
+ if (flags & XATTR_REPLACE) {
+ rc = -ENOENT;
+ goto unlock_out;
+ }
+ nxattr = new_xattr(name, value, size);
+ list_add_tail(&nxattr->list, xlist);
+
+unlock_out:
+ mutex_unlock(&sysfs_xattr_lock);
+ return rc;
+}
+
+ssize_t sysfs_getxattr(struct dentry *dentry, const char *name,
+ void *value, size_t size)
+{
+ struct sysfs_dirent *sd = dentry->d_fsdata;
+ struct list_head *xlist = sd->s_xattr;
+ struct sysfs_xattr *nxattr;
+ int rc = -ENODATA;
+
+ if (!xlist)
+ return -ENODATA;
+
+ mutex_lock(&sysfs_xattr_lock);
+
+ list_for_each_entry(nxattr, xlist, list) {
+ if (!strcmp(nxattr->sx_name, name)) {
+ if (size <= 0) {
+ rc = nxattr->sx_size;
+ goto unlock_out;
+ }
+ if (nxattr->sx_size > size) {
+ rc = -ERANGE;
+ goto unlock_out;
+ }
+ memcpy(value, nxattr->sx_value, nxattr->sx_size);
+ rc = nxattr->sx_size;
+ goto unlock_out;
+ }
+ }
+
+unlock_out:
+ mutex_unlock(&sysfs_xattr_lock);
+ return rc;
+}
+
+ssize_t sysfs_listxattr(struct dentry *dentry, char *buffer, size_t size)
+{
+ struct sysfs_dirent *sd = dentry->d_fsdata;
+ struct list_head *xlist = sd->s_xattr;
+ struct sysfs_xattr *nxattr;
+ ssize_t total = 0;
+ char *cp = buffer;
+
+ if (!xlist)
+ return 0;
+
+ mutex_lock(&sysfs_xattr_lock);
+
+ list_for_each_entry(nxattr, xlist, list)
+ total += strlen(nxattr->sx_name) + 1;
+
+ if (total > size) {
+ total = -ERANGE;
+ goto unlock_out;
+ }
+
+ list_for_each_entry(nxattr, xlist, list) {
+ strcpy(cp, nxattr->sx_name);
+ cp += strlen(nxattr->sx_name) + 1;
+ }
+
+unlock_out:
+ mutex_unlock(&sysfs_xattr_lock);
+ return total;
+}
+
+int sysfs_removexattr(struct dentry *dentry, const char *name)
+{
+ struct sysfs_dirent *sd = dentry->d_fsdata;
+ struct list_head *xlist = sd->s_xattr;
+ struct sysfs_xattr *nxattr;
+ int rc = -ENODATA;
+
+ if (!xlist)
+ return -ENODATA;
+
+ mutex_lock(&sysfs_xattr_lock);
+
+ list_for_each_entry(nxattr, xlist, list) {
+ if (!strcmp(nxattr->sx_name, name)) {
+ list_del(&nxattr->list);
+ if (list_empty(xlist)) {
+ kfree(xlist);
+ sd->s_xattr = NULL;
+ }
+ kfree(nxattr->sx_name);
+ kfree(nxattr->sx_value);
+ kfree(nxattr);
+ rc = 0;
+ goto unlock_out;
+ }
+ }
+
+unlock_out:
+ mutex_unlock(&sysfs_xattr_lock);
+ return rc;
+}
+
+
static inline void set_default_inode_attr(struct inode * inode, mode_t mode)
{
inode->i_mode = mode;
diff -uprN -X linux-2.6/Documentation/dontdiff linux-2.6/fs/sysfs/symlink.c linux-0812/fs/sysfs/symlink.c
--- linux-2.6/fs/sysfs/symlink.c 2009-06-24 20:10:07.000000000 -0700
+++ linux-0812/fs/sysfs/symlink.c 2009-08-12 11:07:52.000000000 -0700
@@ -209,9 +209,13 @@ static void sysfs_put_link(struct dentry
}

const struct inode_operations sysfs_symlink_inode_operations = {
- .readlink = generic_readlink,
- .follow_link = sysfs_follow_link,
- .put_link = sysfs_put_link,
+ .setxattr = sysfs_setxattr,
+ .getxattr = sysfs_getxattr,
+ .listxattr = sysfs_listxattr,
+ .removexattr = sysfs_removexattr,
+ .readlink = generic_readlink,
+ .follow_link = sysfs_follow_link,
+ .put_link = sysfs_put_link,
};


diff -uprN -X linux-2.6/Documentation/dontdiff linux-2.6/fs/sysfs/sysfs.h linux-0812/fs/sysfs/sysfs.h
--- linux-2.6/fs/sysfs/sysfs.h 2009-03-28 13:47:33.000000000 -0700
+++ linux-0812/fs/sysfs/sysfs.h 2009-08-12 11:07:32.000000000 -0700
@@ -31,6 +31,13 @@ struct sysfs_elem_bin_attr {
struct hlist_head buffers;
};

+struct sysfs_xattr {
+ struct list_head list;
+ char *sx_name;
+ char *sx_value;
+ size_t sx_size; /* size of value */
+};
+
/*
* sysfs_dirent - the building block of sysfs hierarchy. Each and
* every sysfs node is represented by single sysfs_dirent.
@@ -57,6 +64,7 @@ struct sysfs_dirent {
ino_t s_ino;
umode_t s_mode;
struct iattr *s_iattr;
+ struct list_head *s_xattr;
};

#define SD_DEACTIVATED_BIAS INT_MIN
@@ -148,6 +156,14 @@ static inline void __sysfs_put(struct sy
struct inode *sysfs_get_inode(struct sysfs_dirent *sd);
void sysfs_delete_inode(struct inode *inode);
int sysfs_setattr(struct dentry *dentry, struct iattr *iattr);
+int sysfs_setxattr(struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags);
+ssize_t sysfs_getxattr(struct dentry *dentry, const char *name,
+ void *value, size_t size);
+ssize_t sysfs_listxattr(struct dentry *dentry, char *buffer, size_t size);
+int sysfs_removexattr(struct dentry *dentry, const char *name);
+
+
int sysfs_hash_and_remove(struct sysfs_dirent *dir_sd, const char *name);
int sysfs_inode_init(void);


--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/