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

From: Darrick J. Wong
Date: Tue Jan 02 2018 - 20:41:29 EST


[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@xxxxxxxxx> 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@xxxxxxxxxx>
> > ---
> > 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