Add the LSM and SELinux infrastructure for invalidating inode->i_security and
for re-initializing it from inode_has_perm when necessary.  In inode_has_perm,
we don't have access to a dentry, so file systems must implement iop->igetxattr
in order to be able to invalidate security labels.

Alternatively, we could add an inode operation called by inode_has_perm to
revalidate the security label of the inode on each call, but inode_has_perm is
called so frequently that the overhead seems excessive.

Signed-off-by: Andreas Gruenbacher <agrue...@redhat.com>
---
 include/linux/lsm_hooks.h         |  6 +++++
 include/linux/security.h          |  5 +++++
 security/selinux/hooks.c          | 47 ++++++++++++++++++++++++++++++---------
 security/selinux/include/objsec.h |  3 ++-
 4 files changed, 49 insertions(+), 12 deletions(-)

diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h
index 9429f05..9fe99d6 100644
--- a/include/linux/lsm_hooks.h
+++ b/include/linux/lsm_hooks.h
@@ -1261,6 +1261,10 @@
  *     audit_rule_init.
  *     @rule contains the allocated rule
  *
+ * @inode_invalidate_secctx:
+ *     Notify the security module that it must revalidate the security context
+ *     of an inode before the next access.
+ *
  * @inode_notifysecctx:
  *     Notify the security module of what the security context of an inode
  *     should be.  Initializes the incore security context managed by the
@@ -1516,6 +1520,7 @@ union security_list_options {
        int (*secctx_to_secid)(const char *secdata, u32 seclen, u32 *secid);
        void (*release_secctx)(char *secdata, u32 seclen);
 
+       void (*inode_invalidate_secctx)(struct inode *inode);
        int (*inode_notifysecctx)(struct inode *inode, void *ctx, u32 ctxlen);
        int (*inode_setsecctx)(struct dentry *dentry, void *ctx, u32 ctxlen);
        int (*inode_getsecctx)(struct inode *inode, void **ctx, u32 *ctxlen);
@@ -1757,6 +1762,7 @@ struct security_hook_heads {
        struct list_head secid_to_secctx;
        struct list_head secctx_to_secid;
        struct list_head release_secctx;
+       struct list_head inode_invalidate_secctx;
        struct list_head inode_notifysecctx;
        struct list_head inode_setsecctx;
        struct list_head inode_getsecctx;
diff --git a/include/linux/security.h b/include/linux/security.h
index 79d85dd..f50587d 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -353,6 +353,7 @@ int security_secid_to_secctx(u32 secid, char **secdata, u32 
*seclen);
 int security_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid);
 void security_release_secctx(char *secdata, u32 seclen);
 
+int security_inode_invalidate_secctx(struct inode *inode);
 int security_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen);
 int security_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen);
 int security_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen);
@@ -1093,6 +1094,10 @@ static inline void security_release_secctx(char 
*secdata, u32 seclen)
 {
 }
 
+static inline void security_inode_invalidate_secctx(struct inode *inode)
+{
+}
+
 static inline int security_inode_notifysecctx(struct inode *inode, void *ctx, 
u32 ctxlen)
 {
        return -EOPNOTSUPP;
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index 564079c..e80fcda 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -430,7 +430,7 @@ static int sb_finish_set_opts(struct super_block *sb)
                        rc = -EOPNOTSUPP;
                        goto out;
                }
-               rc = root_inode->i_op->getxattr(root, XATTR_NAME_SELINUX, NULL, 
0);
+               rc = selinux_getxattr(root_inode, root, NULL, 0);
                if (rc < 0 && rc != -ENODATA) {
                        if (rc == -EOPNOTSUPP)
                                printk(KERN_WARNING "SELinux: (dev %s, type "
@@ -1270,6 +1270,19 @@ static int selinux_genfs_get_sid(struct dentry *dentry,
        return rc;
 }
 
+static int selinux_getxattr(struct inode *inode, struct dentry *dentry,
+                      char *context, unsigned int len)
+{
+       if (dentry)
+               return inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX,
+                                            context, len);
+       else if (inode && inode->i_op->igetxattr)
+               return inode->i_op->igetxattr(inode, XATTR_NAME_SELINUX,
+                                             context, len);
+       else
+               return -EOPNOTSUPP;
+}
+
 /* The inode's security attributes must be initialized before first use. */
 static int inode_doinit_with_dentry(struct inode *inode, struct dentry 
*opt_dentry)
 {
@@ -1282,11 +1295,11 @@ static int inode_doinit_with_dentry(struct inode 
*inode, struct dentry *opt_dent
        unsigned len = 0;
        int rc = 0;
 
-       if (isec->initialized)
+       if (isec->initialized == 1)
                goto out;
 
        mutex_lock(&isec->lock);
-       if (isec->initialized)
+       if (isec->initialized == 1)
                goto out_unlock;
 
        sbsec = inode->i_sb->s_security;
@@ -1319,7 +1332,7 @@ static int inode_doinit_with_dentry(struct inode *inode, 
struct dentry *opt_dent
                        /* Called from selinux_complete_init, try to find a 
dentry. */
                        dentry = d_find_alias(inode);
                }
-               if (!dentry) {
+               if (!dentry && !inode->i_op->igetxattr) {
                        /*
                         * this is can be hit on boot when a file is accessed
                         * before the policy is loaded.  When we load policy we
@@ -1340,14 +1353,12 @@ static int inode_doinit_with_dentry(struct inode 
*inode, struct dentry *opt_dent
                        goto out_unlock;
                }
                context[len] = '\0';
-               rc = inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX,
-                                          context, len);
+               rc = selinux_getxattr(inode, dentry, context, len);
                if (rc == -ERANGE) {
                        kfree(context);
 
                        /* Need a larger buffer.  Query for the right size. */
-                       rc = inode->i_op->getxattr(dentry, XATTR_NAME_SELINUX,
-                                                  NULL, 0);
+                       rc = selinux_getxattr(inode, dentry, NULL, 0);
                        if (rc < 0) {
                                dput(dentry);
                                goto out_unlock;
@@ -1360,9 +1371,7 @@ static int inode_doinit_with_dentry(struct inode *inode, 
struct dentry *opt_dent
                                goto out_unlock;
                        }
                        context[len] = '\0';
-                       rc = inode->i_op->getxattr(dentry,
-                                                  XATTR_NAME_SELINUX,
-                                                  context, len);
+                       rc = selinux_getxattr(inode, dentry, context, len);
                }
                dput(dentry);
                if (rc < 0) {
@@ -1614,6 +1623,12 @@ static int inode_has_perm(const struct cred *cred,
        sid = cred_sid(cred);
        isec = inode->i_security;
 
+       if (isec->initialized == 2) {
+               inode_doinit(inode);
+               if (isec->initialized == 2)
+                       return -EACCES;
+       }
+
        return avc_has_perm(sid, isec->sid, isec->sclass, perms, adp);
 }
 
@@ -5715,6 +5730,15 @@ static void selinux_release_secctx(char *secdata, u32 
seclen)
        kfree(secdata);
 }
 
+static void selinux_inode_invalidate_secctx(struct inode *inode)
+{
+       struct inode_security_struct *isec = inode->i_security;
+
+       mutex_lock(&isec->lock);
+       isec->initialized = 2;
+       mutex_unlock(&isec->lock);
+}
+
 /*
  *     called with inode->i_mutex locked
  */
@@ -5946,6 +5970,7 @@ static struct security_hook_list selinux_hooks[] = {
        LSM_HOOK_INIT(secid_to_secctx, selinux_secid_to_secctx),
        LSM_HOOK_INIT(secctx_to_secid, selinux_secctx_to_secid),
        LSM_HOOK_INIT(release_secctx, selinux_release_secctx),
+       LSM_HOOK_INIT(inode_invalidate_secctx, selinux_inode_invalidate_secctx),
        LSM_HOOK_INIT(inode_notifysecctx, selinux_inode_notifysecctx),
        LSM_HOOK_INIT(inode_setsecctx, selinux_inode_setsecctx),
        LSM_HOOK_INIT(inode_getsecctx, selinux_inode_getsecctx),
diff --git a/security/selinux/include/objsec.h 
b/security/selinux/include/objsec.h
index 81fa718..5b13732 100644
--- a/security/selinux/include/objsec.h
+++ b/security/selinux/include/objsec.h
@@ -46,7 +46,8 @@ struct inode_security_struct {
        u32 task_sid;           /* SID of creating task */
        u32 sid;                /* SID of this object */
        u16 sclass;             /* security class of this object */
-       unsigned char initialized;      /* initialization flag */
+       unsigned char initialized;      /* 0: not initialized, 1: initialized,
+                                          2: needs revalidation */
        struct mutex lock;
 };
 
-- 
2.4.3

Reply via email to