[PATCHv6 1/1] ima: re-introduce own integrity cache lock

Darrick J. Wong darrick.wong at oracle.com
Wed Jan 3 01:40:48 UTC 2018


[might as well cc linux-xfs]

On Thu, Dec 14, 2017 at 12:22:37AM +0200, Dmitry Kasatkin wrote:
> Hi,
> 
> Could I ask FS maintainers to test IMA with this patch additionally
> and provide ack/tested.
> We tested but may be you have and some special testing.

Super-late to this party, but unless xfstests has automated tests to
set up IMA on top of an existing filesystem then I most likely have no
idea /how/ to test IMA.  I did a quick grep of xfstests git and I don't
see anything IMA-related.

--D

> 
> Thanks in advance,
> Dmitry
> 
> On Tue, Dec 5, 2017 at 9:06 PM, Dmitry Kasatkin
> <dmitry.kasatkin at gmail.com> wrote:
> > The original design was discussed 3+ years ago, but was never completed/upstreamed.
> > Based on the recent discussions with Linus
> > https://patchwork.kernel.org/patch/9975919, I've rebased this patch.
> >
> > Before IMA appraisal was introduced, IMA was using own integrity cache
> > lock along with i_mutex. process_measurement and ima_file_free took
> > the iint->mutex first and then the i_mutex, while setxattr, chmod and
> > chown took the locks in reverse order. To resolve the potential deadlock,
> > i_mutex was moved to protect entire IMA functionality and the redundant
> > iint->mutex was eliminated.
> >
> > Solution was based on the assumption that filesystem code does not take
> > i_mutex further. But when file is opened with O_DIRECT flag, direct-io
> > implementation takes i_mutex and produces deadlock. Furthermore, certain
> > other filesystem operations, such as llseek, also take i_mutex.
> >
> > More recently some filesystems have replaced their filesystem specific
> > lock with the global i_rwsem to read a file.  As a result, when IMA
> > attempts to calculate the file hash, reading the file attempts to take
> > the i_rwsem again.
> >
> > To resolve O_DIRECT related deadlock problem, this patch re-introduces
> > iint->mutex. But to eliminate the original chmod() related deadlock
> > problem, this patch eliminates the requirement for chmod hooks to take
> > the iint->mutex by introducing additional atomic iint->attr_flags to
> > indicate calling of the hooks. The allowed locking order is to take
> > the iint->mutex first and then the i_rwsem.
> >
> > Original flags were cleared in chmod(), setxattr() or removwxattr() hooks
> > and tested when file was closed or opened again. New atomic flags are set
> > or cleared in those hooks and tested to clear iint->flags on close or on open.
> >
> > Atomic flags are following:
> > * IMA_CHANGE_ATTR - indicates that chATTR() was called (chmod, chown, chgrp)
> >   and file attributes have changed. On file open, it causes IMA to clear
> >   iint->flags to re-evaluate policy and perform IMA functions again.
> > * IMA_CHANGE_XATTR - indicates that setxattr or removexattr was called and
> >   extended attributes have changed. On file open, it causes IMA to clear
> >   iint->flags IMA_DONE_MASK to re-appraise.
> > * IMA_UPDATE_XATTR - indicates that security.ima needs to be updated.
> >   It is cleared if file policy changes and no update is needed.
> > * IMA_DIGSIG - indicates that file security.ima has signature and file
> >   security.ima must not update to file has on file close.
> > * IMA_MUST_MEASURE - indicates the file is in the measurement policy.
> >
> > Changes in v6:
> > * introduce the atomic flag IMA_MUST_MEASURE to indicate that a file is in
> >   the measurement policy. It is used instead of the IMA_MEASURE (iint->flags)
> >   to detect ToMToU violation and when iint->mutex is unlocked and behind inode
> >   lock only (same as some other flags). Issue reported by Roberto Sassu.
> >
> > Changes in v5:
> > * use of inode_lock() and inode_unlock()
> >
> > Changes in v4:
> > * adoped to violation detection fixes
> > * added IMA_UPDATE_XATTR flag to require xattr update on file close
> >
> > Changes in v3:
> > * prevent signature removal with new locking
> > * rename attr_flags to atomic_flags
> >
> > Changes in v2:
> > * revert taking the i_mutex in integrity_inode_get() so that iint allocation
> >   could be done with i_mutex taken
> > * move taking the i_mutex from appraisal code to the process_measurement()
> >
> > Signed-off-by: Dmitry Kasatkin <dmitry.kasatkin at huawei.com>
> > ---
> >  security/integrity/iint.c             |  2 +
> >  security/integrity/ima/ima_appraise.c | 27 +++++++-------
> >  security/integrity/ima/ima_main.c     | 70 ++++++++++++++++++++++++-----------
> >  security/integrity/integrity.h        | 18 ++++++---
> >  4 files changed, 77 insertions(+), 40 deletions(-)
> >
> > diff --git a/security/integrity/iint.c b/security/integrity/iint.c
> > index c84e058..d726ba23 100644
> > --- a/security/integrity/iint.c
> > +++ b/security/integrity/iint.c
> > @@ -155,12 +155,14 @@ static void init_once(void *foo)
> >         memset(iint, 0, sizeof(*iint));
> >         iint->version = 0;
> >         iint->flags = 0UL;
> > +       iint->atomic_flags = 0;
> >         iint->ima_file_status = INTEGRITY_UNKNOWN;
> >         iint->ima_mmap_status = INTEGRITY_UNKNOWN;
> >         iint->ima_bprm_status = INTEGRITY_UNKNOWN;
> >         iint->ima_read_status = INTEGRITY_UNKNOWN;
> >         iint->evm_status = INTEGRITY_UNKNOWN;
> >         iint->measured_pcrs = 0;
> > +       mutex_init(&iint->mutex);
> >  }
> >
> >  static int __init integrity_iintcache_init(void)
> > diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c
> > index 9a54c77..3fc96dbd 100644
> > --- a/security/integrity/ima/ima_appraise.c
> > +++ b/security/integrity/ima/ima_appraise.c
> > @@ -251,6 +251,7 @@ int ima_appraise_measurement(enum ima_hooks func,
> >                         status = INTEGRITY_FAIL;
> >                         break;
> >                 }
> > +               clear_bit(IMA_DIGSIG, &iint->atomic_flags);
> >                 if (xattr_len - sizeof(xattr_value->type) - hash_start >=
> >                                 iint->ima_hash->length)
> >                         /* xattr length may be longer. md5 hash in previous
> > @@ -269,7 +270,7 @@ int ima_appraise_measurement(enum ima_hooks func,
> >                 status = INTEGRITY_PASS;
> >                 break;
> >         case EVM_IMA_XATTR_DIGSIG:
> > -               iint->flags |= IMA_DIGSIG;
> > +               set_bit(IMA_DIGSIG, &iint->atomic_flags);
> >                 rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA,
> >                                              (const char *)xattr_value, rc,
> >                                              iint->ima_hash->digest,
> > @@ -320,14 +321,16 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file)
> >         int rc = 0;
> >
> >         /* do not collect and update hash for digital signatures */
> > -       if (iint->flags & IMA_DIGSIG)
> > +       if (test_bit(IMA_DIGSIG, &iint->atomic_flags))
> >                 return;
> >
> >         rc = ima_collect_measurement(iint, file, NULL, 0, ima_hash_algo);
> >         if (rc < 0)
> >                 return;
> >
> > +       inode_lock(file_inode(file));
> >         ima_fix_xattr(dentry, iint);
> > +       inode_unlock(file_inode(file));
> >  }
> >
> >  /**
> > @@ -350,16 +353,14 @@ void ima_inode_post_setattr(struct dentry *dentry)
> >                 return;
> >
> >         must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR);
> > +       if (!must_appraise)
> > +               __vfs_removexattr(dentry, XATTR_NAME_IMA);
> >         iint = integrity_iint_find(inode);
> >         if (iint) {
> > -               iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED |
> > -                                IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK |
> > -                                IMA_ACTION_RULE_FLAGS);
> > -               if (must_appraise)
> > -                       iint->flags |= IMA_APPRAISE;
> > +               set_bit(IMA_CHANGE_ATTR, &iint->atomic_flags);
> > +               if (!must_appraise)
> > +                       clear_bit(IMA_UPDATE_XATTR, &iint->atomic_flags);
> >         }
> > -       if (!must_appraise)
> > -               __vfs_removexattr(dentry, XATTR_NAME_IMA);
> >  }
> >
> >  /*
> > @@ -388,12 +389,12 @@ static void ima_reset_appraise_flags(struct inode *inode, int digsig)
> >         iint = integrity_iint_find(inode);
> >         if (!iint)
> >                 return;
> > -
> > -       iint->flags &= ~IMA_DONE_MASK;
> >         iint->measured_pcrs = 0;
> > +       set_bit(IMA_CHANGE_XATTR, &iint->atomic_flags);
> >         if (digsig)
> > -               iint->flags |= IMA_DIGSIG;
> > -       return;
> > +               set_bit(IMA_DIGSIG, &iint->atomic_flags);
> > +       else
> > +               clear_bit(IMA_DIGSIG, &iint->atomic_flags);
> >  }
> >
> >  int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name,
> > diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c
> > index 7706546..edf4e07 100644
> > --- a/security/integrity/ima/ima_main.c
> > +++ b/security/integrity/ima/ima_main.c
> > @@ -96,10 +96,13 @@ static void ima_rdwr_violation_check(struct file *file,
> >                         if (!iint)
> >                                 iint = integrity_iint_find(inode);
> >                         /* IMA_MEASURE is set from reader side */
> > -                       if (iint && (iint->flags & IMA_MEASURE))
> > +                       if (iint && test_bit(IMA_MUST_MEASURE,
> > +                                               &iint->atomic_flags))
> >                                 send_tomtou = true;
> >                 }
> >         } else {
> > +               if (must_measure)
> > +                       set_bit(IMA_MUST_MEASURE, &iint->atomic_flags);
> >                 if ((atomic_read(&inode->i_writecount) > 0) && must_measure)
> >                         send_writers = true;
> >         }
> > @@ -121,21 +124,24 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint,
> >                                   struct inode *inode, struct file *file)
> >  {
> >         fmode_t mode = file->f_mode;
> > +       bool update;
> >
> >         if (!(mode & FMODE_WRITE))
> >                 return;
> >
> > -       inode_lock(inode);
> > +       mutex_lock(&iint->mutex);
> >         if (atomic_read(&inode->i_writecount) == 1) {
> > +               update = test_and_clear_bit(IMA_UPDATE_XATTR,
> > +                                           &iint->atomic_flags);
> >                 if ((iint->version != inode->i_version) ||
> >                     (iint->flags & IMA_NEW_FILE)) {
> >                         iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE);
> >                         iint->measured_pcrs = 0;
> > -                       if (iint->flags & IMA_APPRAISE)
> > +                       if (update)
> >                                 ima_update_xattr(iint, file);
> >                 }
> >         }
> > -       inode_unlock(inode);
> > +       mutex_unlock(&iint->mutex);
> >  }
> >
> >  /**
> > @@ -168,7 +174,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
> >         char *pathbuf = NULL;
> >         char filename[NAME_MAX];
> >         const char *pathname = NULL;
> > -       int rc = -ENOMEM, action, must_appraise;
> > +       int rc = 0, action, must_appraise = 0;
> >         int pcr = CONFIG_IMA_MEASURE_PCR_IDX;
> >         struct evm_ima_xattr_data *xattr_value = NULL;
> >         int xattr_len = 0;
> > @@ -199,17 +205,31 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
> >         if (action) {
> >                 iint = integrity_inode_get(inode);
> >                 if (!iint)
> > -                       goto out;
> > +                       rc = -ENOMEM;
> >         }
> >
> > -       if (violation_check) {
> > +       if (!rc && violation_check)
> >                 ima_rdwr_violation_check(file, iint, action & IMA_MEASURE,
> >                                          &pathbuf, &pathname);
> > -               if (!action) {
> > -                       rc = 0;
> > -                       goto out_free;
> > -               }
> > -       }
> > +
> > +       inode_unlock(inode);
> > +
> > +       if (rc)
> > +               goto out;
> > +       if (!action)
> > +               goto out;
> > +
> > +       mutex_lock(&iint->mutex);
> > +
> > +       if (test_and_clear_bit(IMA_CHANGE_ATTR, &iint->atomic_flags))
> > +               /* reset appraisal flags if ima_inode_post_setattr was called */
> > +               iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED |
> > +                                IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK |
> > +                                IMA_ACTION_FLAGS);
> > +
> > +       if (test_and_clear_bit(IMA_CHANGE_XATTR, &iint->atomic_flags))
> > +               /* reset all flags if ima_inode_setxattr was called */
> > +               iint->flags &= ~IMA_DONE_MASK;
> >
> >         /* Determine if already appraised/measured based on bitmask
> >          * (IMA_MEASURE, IMA_MEASURED, IMA_XXXX_APPRAISE, IMA_XXXX_APPRAISED,
> > @@ -227,7 +247,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
> >         if (!action) {
> >                 if (must_appraise)
> >                         rc = ima_get_cache_status(iint, func);
> > -               goto out_digsig;
> > +               goto out_locked;
> >         }
> >
> >         template_desc = ima_template_desc_current();
> > @@ -240,7 +260,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
> >
> >         rc = ima_collect_measurement(iint, file, buf, size, hash_algo);
> >         if (rc != 0 && rc != -EBADF && rc != -EINVAL)
> > -               goto out_digsig;
> > +               goto out_locked;
> >
> >         if (!pathbuf)   /* ima_rdwr_violation possibly pre-fetched */
> >                 pathname = ima_d_path(&file->f_path, &pathbuf, filename);
> > @@ -248,26 +268,32 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
> >         if (action & IMA_MEASURE)
> >                 ima_store_measurement(iint, file, pathname,
> >                                       xattr_value, xattr_len, pcr);
> > -       if (rc == 0 && (action & IMA_APPRAISE_SUBMASK))
> > +       if (rc == 0 && (action & IMA_APPRAISE_SUBMASK)) {
> > +               inode_lock(inode);
> >                 rc = ima_appraise_measurement(func, iint, file, pathname,
> >                                               xattr_value, xattr_len, opened);
> > +               inode_unlock(inode);
> > +       }
> >         if (action & IMA_AUDIT)
> >                 ima_audit_measurement(iint, pathname);
> >
> >         if ((file->f_flags & O_DIRECT) && (iint->flags & IMA_PERMIT_DIRECTIO))
> >                 rc = 0;
> > -out_digsig:
> > -       if ((mask & MAY_WRITE) && (iint->flags & IMA_DIGSIG) &&
> > +out_locked:
> > +       if ((mask & MAY_WRITE) && test_bit(IMA_DIGSIG, &iint->atomic_flags) &&
> >              !(iint->flags & IMA_NEW_FILE))
> >                 rc = -EACCES;
> > +       mutex_unlock(&iint->mutex);
> >         kfree(xattr_value);
> > -out_free:
> > +out:
> >         if (pathbuf)
> >                 __putname(pathbuf);
> > -out:
> > -       inode_unlock(inode);
> > -       if ((rc && must_appraise) && (ima_appraise & IMA_APPRAISE_ENFORCE))
> > -               return -EACCES;
> > +       if (must_appraise) {
> > +               if (rc && (ima_appraise & IMA_APPRAISE_ENFORCE))
> > +                       return -EACCES;
> > +               if (file->f_mode & FMODE_WRITE)
> > +                       set_bit(IMA_UPDATE_XATTR, &iint->atomic_flags);
> > +       }
> >         return 0;
> >  }
> >
> > diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h
> > index e324bf9..c64ea8f 100644
> > --- a/security/integrity/integrity.h
> > +++ b/security/integrity/integrity.h
> > @@ -29,11 +29,10 @@
> >  /* iint cache flags */
> >  #define IMA_ACTION_FLAGS       0xff000000
> >  #define IMA_ACTION_RULE_FLAGS  0x06000000
> > -#define IMA_DIGSIG             0x01000000
> > -#define IMA_DIGSIG_REQUIRED    0x02000000
> > -#define IMA_PERMIT_DIRECTIO    0x04000000
> > -#define IMA_NEW_FILE           0x08000000
> > -#define EVM_IMMUTABLE_DIGSIG   0x10000000
> > +#define IMA_DIGSIG_REQUIRED    0x01000000
> > +#define IMA_PERMIT_DIRECTIO    0x02000000
> > +#define IMA_NEW_FILE           0x04000000
> > +#define EVM_IMMUTABLE_DIGSIG   0x08000000
> >
> >  #define IMA_DO_MASK            (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \
> >                                  IMA_APPRAISE_SUBMASK)
> > @@ -54,6 +53,13 @@
> >  #define IMA_APPRAISED_SUBMASK  (IMA_FILE_APPRAISED | IMA_MMAP_APPRAISED | \
> >                                  IMA_BPRM_APPRAISED | IMA_READ_APPRAISED)
> >
> > +/* iint cache atomic_flags */
> > +#define IMA_CHANGE_XATTR       0
> > +#define IMA_UPDATE_XATTR       1
> > +#define IMA_CHANGE_ATTR                2
> > +#define IMA_DIGSIG             3
> > +#define IMA_MUST_MEASURE       4
> > +
> >  enum evm_ima_xattr_type {
> >         IMA_XATTR_DIGEST = 0x01,
> >         EVM_XATTR_HMAC,
> > @@ -102,10 +108,12 @@ struct signature_v2_hdr {
> >  /* integrity data associated with an inode */
> >  struct integrity_iint_cache {
> >         struct rb_node rb_node; /* rooted in integrity_iint_tree */
> > +       struct mutex mutex;     /* protects: version, flags, digest */
> >         struct inode *inode;    /* back pointer to inode in question */
> >         u64 version;            /* track inode changes */
> >         unsigned long flags;
> >         unsigned long measured_pcrs;
> > +       unsigned long atomic_flags;
> >         enum integrity_status ima_file_status:4;
> >         enum integrity_status ima_mmap_status:4;
> >         enum integrity_status ima_bprm_status:4;
> > --
> > 2.7.4
> >
> 
> 
> 
> -- 
> Thanks,
> Dmitry
--
To unsubscribe from this list: send the line "unsubscribe linux-security-module" in
the body of a message to majordomo at vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html



More information about the Linux-security-module-archive mailing list