When you

        setfcaps -c cap_net_admin=p -e /bin/ping
        cp /bin/sh /bin/ping

then /bin/ping should lose its file capabilities.  This patch probably
will need to be cleaned up, but seems to work as it should.

thanks,
-serge

>From f546dc74fac4bfd6374924a6bf21815d1d857ba8 Mon Sep 17 00:00:00 2001
From: Serge E. Hallyn <[EMAIL PROTECTED]>
Date: Fri, 27 Jul 2007 10:56:10 -0400
Subject: [PATCH RFC] file capabilities: clear fcaps on inode change

When a file with posix capabilities is overwritten, the
file capabilities, like a setuid bit, should be removed.

Signed-off-by: Serge E. Hallyn <[EMAIL PROTECTED]>
---
 fs/attr.c          |   12 ++++++++++++
 fs/nfsd/vfs.c      |    4 ++--
 fs/open.c          |    3 ++-
 fs/splice.c        |   10 ++++++++++
 fs/xattr.c         |   27 +++++++++++++++++++++++++++
 include/linux/fs.h |    1 +
 mm/filemap.c       |   24 ++++++++++++++++++++++--
 7 files changed, 76 insertions(+), 5 deletions(-)

diff --git a/fs/attr.c b/fs/attr.c
index f8dfc22..e4ca694 100644
--- a/fs/attr.c
+++ b/fs/attr.c
@@ -100,6 +100,9 @@ int inode_setattr(struct inode * inode, struct iattr * attr)
 }
 EXPORT_SYMBOL(inode_setattr);
 
+int file_has_capabilities(struct dentry *dentry);
+int remove_file_capabilities(struct dentry *dentry);
+
 int notify_change(struct dentry * dentry, struct iattr * attr)
 {
        struct inode *inode = dentry->d_inode;
@@ -116,6 +119,15 @@ int notify_change(struct dentry * dentry, struct iattr * 
attr)
                attr->ia_atime = now;
        if (!(ia_valid & ATTR_MTIME_SET))
                attr->ia_mtime = now;
+       if (ia_valid & ATTR_KILL_CAP) {
+               attr->ia_valid &= ~ATTR_KILL_CAP;
+               ia_valid &= ~ATTR_KILL_CAP;
+               if (file_has_capabilities(dentry)) {
+                       error = remove_file_capabilities(dentry);
+                       if (error)
+                               return error;
+               }
+       }
        if (ia_valid & ATTR_KILL_SUID) {
                attr->ia_valid &= ~ATTR_KILL_SUID;
                if (mode & S_ISUID) {
diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c
index ee96a89..3b3c5b5 100644
--- a/fs/nfsd/vfs.c
+++ b/fs/nfsd/vfs.c
@@ -364,7 +364,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, 
struct iattr *iap,
 
        /* Revoke setuid/setgid bit on chown/chgrp */
        if ((iap->ia_valid & ATTR_UID) && iap->ia_uid != inode->i_uid)
-               iap->ia_valid |= ATTR_KILL_SUID;
+               iap->ia_valid |= ATTR_KILL_SUID | ATTR_KILL_CAP;
        if ((iap->ia_valid & ATTR_GID) && iap->ia_gid != inode->i_gid)
                iap->ia_valid |= ATTR_KILL_SGID;
 
@@ -921,7 +921,7 @@ out:
 static void kill_suid(struct dentry *dentry)
 {
        struct iattr    ia;
-       ia.ia_valid = ATTR_KILL_SUID | ATTR_KILL_SGID;
+       ia.ia_valid = ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_CAP;
 
        mutex_lock(&dentry->d_inode->i_mutex);
        notify_change(dentry, &ia);
diff --git a/fs/open.c b/fs/open.c
index 0e2e7b1..1524854 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -658,7 +658,8 @@ static int chown_common(struct dentry * dentry, uid_t user, 
gid_t group)
                newattrs.ia_gid = group;
        }
        if (!S_ISDIR(inode->i_mode))
-               newattrs.ia_valid |= ATTR_KILL_SUID|ATTR_KILL_SGID;
+               newattrs.ia_valid |=
+                       ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_CAP;
        mutex_lock(&inode->i_mutex);
        error = notify_change(dentry, &newattrs);
        mutex_unlock(&inode->i_mutex);
diff --git a/fs/splice.c b/fs/splice.c
index e36c003..77a7da5 100644
--- a/fs/splice.c
+++ b/fs/splice.c
@@ -805,6 +805,8 @@ generic_file_splice_write_nolock(struct pipe_inode_info 
*pipe, struct file *out,
 
 EXPORT_SYMBOL(generic_file_splice_write_nolock);
 
+int remove_file_capabilities(struct dentry *dentry);
+int should_remove_cap(struct dentry *);
 /**
  * generic_file_splice_write - splice data from a pipe to a file
  * @pipe:      pipe info
@@ -835,6 +837,14 @@ generic_file_splice_write(struct pipe_inode_info *pipe, 
struct file *out,
                if (err)
                        return err;
        }
+       err = should_remove_cap(out->f_path.dentry);
+       if (unlikely(err)) {
+               mutex_lock(&inode->i_mutex);
+               err = remove_file_capabilities(out->f_path.dentry);
+               mutex_unlock(&inode->i_mutex);
+               if (err)
+                       return err;
+       }
 
        ret = splice_from_pipe(pipe, out, ppos, len, flags, pipe_to_file);
        if (ret > 0) {
diff --git a/fs/xattr.c b/fs/xattr.c
index 6645b73..0ec2266 100644
--- a/fs/xattr.c
+++ b/fs/xattr.c
@@ -618,3 +618,30 @@ EXPORT_SYMBOL(generic_getxattr);
 EXPORT_SYMBOL(generic_listxattr);
 EXPORT_SYMBOL(generic_setxattr);
 EXPORT_SYMBOL(generic_removexattr);
+
+int file_has_capabilities(struct dentry *dentry)
+{
+       struct inode *inode = dentry->d_inode;
+       int error;
+
+       if (!inode->i_op || !inode->i_op->getxattr)
+               return 0;
+
+       error = inode->i_op->getxattr(dentry, XATTR_NAME_CAPS, NULL, 0);
+       if (error > 0)
+               return 1;
+       return 0;
+}
+
+int remove_file_capabilities(struct dentry *dentry)
+{
+       struct inode *inode = dentry->d_inode;
+       int error;
+
+       if (!inode->i_op || !inode->i_op->removexattr)
+               return 0;
+
+       error = inode->i_op->removexattr(dentry, XATTR_NAME_CAPS);
+
+       return error;
+}
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 3d99c04..3f4165b 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -335,6 +335,7 @@ typedef void (dio_iodone_t)(struct kiocb *iocb, loff_t 
offset,
 #define ATTR_KILL_SUID 2048
 #define ATTR_KILL_SGID 4096
 #define ATTR_FILE      8192
+#define ATTR_KILL_CAP  16384
 
 /*
  * This is the Inode Attributes structure, used for notify_change().  It
diff --git a/mm/filemap.c b/mm/filemap.c
index c0774b5..041eba6 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1637,6 +1637,19 @@ int should_remove_suid(struct dentry *dentry)
 }
 EXPORT_SYMBOL(should_remove_suid);
 
+/*
+ * we always remove file capabilities if the file is overwritten,
+ * regardless which capabilities the writer may have.
+ */
+extern int file_has_capabilities(struct dentry *dentry);
+int should_remove_cap(struct dentry *dentry)
+{
+       if (file_has_capabilities(dentry))
+               return ATTR_KILL_CAP;
+
+       return 0;
+}
+
 int __remove_suid(struct dentry *dentry, int kill)
 {
        struct iattr newattrs;
@@ -1645,14 +1658,21 @@ int __remove_suid(struct dentry *dentry, int kill)
        return notify_change(dentry, &newattrs);
 }
 
+int remove_file_capabilities(struct dentry *dentry);
 int remove_suid(struct dentry *dentry)
 {
        int kill = should_remove_suid(dentry);
+       int ret = 0;
 
        if (unlikely(kill))
-               return __remove_suid(dentry, kill);
+               ret = __remove_suid(dentry, kill);
+       if (ret)
+               return ret;
 
-       return 0;
+       if (should_remove_cap(dentry))
+               ret = remove_file_capabilities(dentry);
+
+       return ret;
 }
 EXPORT_SYMBOL(remove_suid);
 
-- 
1.5.1.1.GIT

-
To unsubscribe from this list: send the line "unsubscribe 
linux-security-module" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to