Commit 0d73a552 authored by Dmitry Kasatkin's avatar Dmitry Kasatkin Committed by Mimi Zohar

ima: re-introduce own integrity cache lock

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.

Fixes: Commit 65523218 ("xfs: remove i_iolock and use i_rwsem in
the VFS inode instead")
Signed-off-by: default avatarDmitry Kasatkin <dmitry.kasatkin@huawei.com>
Signed-off-by: default avatarMimi Zohar <zohar@linux.vnet.ibm.com>
parent 50b97748
...@@ -155,12 +155,14 @@ static void init_once(void *foo) ...@@ -155,12 +155,14 @@ static void init_once(void *foo)
memset(iint, 0, sizeof(*iint)); memset(iint, 0, sizeof(*iint));
iint->version = 0; iint->version = 0;
iint->flags = 0UL; iint->flags = 0UL;
iint->atomic_flags = 0;
iint->ima_file_status = INTEGRITY_UNKNOWN; iint->ima_file_status = INTEGRITY_UNKNOWN;
iint->ima_mmap_status = INTEGRITY_UNKNOWN; iint->ima_mmap_status = INTEGRITY_UNKNOWN;
iint->ima_bprm_status = INTEGRITY_UNKNOWN; iint->ima_bprm_status = INTEGRITY_UNKNOWN;
iint->ima_read_status = INTEGRITY_UNKNOWN; iint->ima_read_status = INTEGRITY_UNKNOWN;
iint->evm_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN;
iint->measured_pcrs = 0; iint->measured_pcrs = 0;
mutex_init(&iint->mutex);
} }
static int __init integrity_iintcache_init(void) static int __init integrity_iintcache_init(void)
......
...@@ -251,6 +251,7 @@ int ima_appraise_measurement(enum ima_hooks func, ...@@ -251,6 +251,7 @@ int ima_appraise_measurement(enum ima_hooks func,
status = INTEGRITY_FAIL; status = INTEGRITY_FAIL;
break; break;
} }
clear_bit(IMA_DIGSIG, &iint->atomic_flags);
if (xattr_len - sizeof(xattr_value->type) - hash_start >= if (xattr_len - sizeof(xattr_value->type) - hash_start >=
iint->ima_hash->length) iint->ima_hash->length)
/* xattr length may be longer. md5 hash in previous /* xattr length may be longer. md5 hash in previous
...@@ -269,7 +270,7 @@ int ima_appraise_measurement(enum ima_hooks func, ...@@ -269,7 +270,7 @@ int ima_appraise_measurement(enum ima_hooks func,
status = INTEGRITY_PASS; status = INTEGRITY_PASS;
break; break;
case EVM_IMA_XATTR_DIGSIG: case EVM_IMA_XATTR_DIGSIG:
iint->flags |= IMA_DIGSIG; set_bit(IMA_DIGSIG, &iint->atomic_flags);
rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA,
(const char *)xattr_value, rc, (const char *)xattr_value, rc,
iint->ima_hash->digest, iint->ima_hash->digest,
...@@ -320,7 +321,7 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) ...@@ -320,7 +321,7 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file)
int rc = 0; int rc = 0;
/* do not collect and update hash for digital signatures */ /* do not collect and update hash for digital signatures */
if (iint->flags & IMA_DIGSIG) if (test_bit(IMA_DIGSIG, &iint->atomic_flags))
return; return;
if (iint->ima_file_status != INTEGRITY_PASS) if (iint->ima_file_status != INTEGRITY_PASS)
...@@ -330,7 +331,9 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) ...@@ -330,7 +331,9 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file)
if (rc < 0) if (rc < 0)
return; return;
inode_lock(file_inode(file));
ima_fix_xattr(dentry, iint); ima_fix_xattr(dentry, iint);
inode_unlock(file_inode(file));
} }
/** /**
...@@ -353,16 +356,14 @@ void ima_inode_post_setattr(struct dentry *dentry) ...@@ -353,16 +356,14 @@ void ima_inode_post_setattr(struct dentry *dentry)
return; return;
must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR);
if (!must_appraise)
__vfs_removexattr(dentry, XATTR_NAME_IMA);
iint = integrity_iint_find(inode); iint = integrity_iint_find(inode);
if (iint) { if (iint) {
iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED | set_bit(IMA_CHANGE_ATTR, &iint->atomic_flags);
IMA_APPRAISE_SUBMASK | IMA_APPRAISED_SUBMASK | if (!must_appraise)
IMA_ACTION_RULE_FLAGS); clear_bit(IMA_UPDATE_XATTR, &iint->atomic_flags);
if (must_appraise)
iint->flags |= IMA_APPRAISE;
} }
if (!must_appraise)
__vfs_removexattr(dentry, XATTR_NAME_IMA);
} }
/* /*
...@@ -391,12 +392,12 @@ static void ima_reset_appraise_flags(struct inode *inode, int digsig) ...@@ -391,12 +392,12 @@ static void ima_reset_appraise_flags(struct inode *inode, int digsig)
iint = integrity_iint_find(inode); iint = integrity_iint_find(inode);
if (!iint) if (!iint)
return; return;
iint->flags &= ~IMA_DONE_MASK;
iint->measured_pcrs = 0; iint->measured_pcrs = 0;
set_bit(IMA_CHANGE_XATTR, &iint->atomic_flags);
if (digsig) if (digsig)
iint->flags |= IMA_DIGSIG; set_bit(IMA_DIGSIG, &iint->atomic_flags);
return; else
clear_bit(IMA_DIGSIG, &iint->atomic_flags);
} }
int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name, int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name,
......
...@@ -96,10 +96,13 @@ static void ima_rdwr_violation_check(struct file *file, ...@@ -96,10 +96,13 @@ static void ima_rdwr_violation_check(struct file *file,
if (!iint) if (!iint)
iint = integrity_iint_find(inode); iint = integrity_iint_find(inode);
/* IMA_MEASURE is set from reader side */ /* 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; send_tomtou = true;
} }
} else { } else {
if (must_measure)
set_bit(IMA_MUST_MEASURE, &iint->atomic_flags);
if ((atomic_read(&inode->i_writecount) > 0) && must_measure) if ((atomic_read(&inode->i_writecount) > 0) && must_measure)
send_writers = true; send_writers = true;
} }
...@@ -121,21 +124,24 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint, ...@@ -121,21 +124,24 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint,
struct inode *inode, struct file *file) struct inode *inode, struct file *file)
{ {
fmode_t mode = file->f_mode; fmode_t mode = file->f_mode;
bool update;
if (!(mode & FMODE_WRITE)) if (!(mode & FMODE_WRITE))
return; return;
inode_lock(inode); mutex_lock(&iint->mutex);
if (atomic_read(&inode->i_writecount) == 1) { if (atomic_read(&inode->i_writecount) == 1) {
update = test_and_clear_bit(IMA_UPDATE_XATTR,
&iint->atomic_flags);
if ((iint->version != inode->i_version) || if ((iint->version != inode->i_version) ||
(iint->flags & IMA_NEW_FILE)) { (iint->flags & IMA_NEW_FILE)) {
iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE); iint->flags &= ~(IMA_DONE_MASK | IMA_NEW_FILE);
iint->measured_pcrs = 0; iint->measured_pcrs = 0;
if (iint->flags & IMA_APPRAISE) if (update)
ima_update_xattr(iint, file); 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, ...@@ -168,7 +174,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
char *pathbuf = NULL; char *pathbuf = NULL;
char filename[NAME_MAX]; char filename[NAME_MAX];
const char *pathname = NULL; const char *pathname = NULL;
int rc = -ENOMEM, action, must_appraise; int rc = 0, action, must_appraise = 0;
int pcr = CONFIG_IMA_MEASURE_PCR_IDX; int pcr = CONFIG_IMA_MEASURE_PCR_IDX;
struct evm_ima_xattr_data *xattr_value = NULL; struct evm_ima_xattr_data *xattr_value = NULL;
int xattr_len = 0; int xattr_len = 0;
...@@ -199,17 +205,31 @@ static int process_measurement(struct file *file, char *buf, loff_t size, ...@@ -199,17 +205,31 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
if (action) { if (action) {
iint = integrity_inode_get(inode); iint = integrity_inode_get(inode);
if (!iint) if (!iint)
goto out; rc = -ENOMEM;
} }
if (violation_check) { if (!rc && violation_check)
ima_rdwr_violation_check(file, iint, action & IMA_MEASURE, ima_rdwr_violation_check(file, iint, action & IMA_MEASURE,
&pathbuf, &pathname); &pathbuf, &pathname);
if (!action) {
rc = 0; inode_unlock(inode);
goto out_free;
} 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 /* Determine if already appraised/measured based on bitmask
* (IMA_MEASURE, IMA_MEASURED, IMA_XXXX_APPRAISE, IMA_XXXX_APPRAISED, * (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, ...@@ -227,7 +247,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
if (!action) { if (!action) {
if (must_appraise) if (must_appraise)
rc = ima_get_cache_status(iint, func); rc = ima_get_cache_status(iint, func);
goto out_digsig; goto out_locked;
} }
template_desc = ima_template_desc_current(); template_desc = ima_template_desc_current();
...@@ -240,7 +260,7 @@ static int process_measurement(struct file *file, char *buf, loff_t size, ...@@ -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); rc = ima_collect_measurement(iint, file, buf, size, hash_algo);
if (rc != 0 && rc != -EBADF && rc != -EINVAL) if (rc != 0 && rc != -EBADF && rc != -EINVAL)
goto out_digsig; goto out_locked;
if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */ if (!pathbuf) /* ima_rdwr_violation possibly pre-fetched */
pathname = ima_d_path(&file->f_path, &pathbuf, filename); 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, ...@@ -248,26 +268,32 @@ static int process_measurement(struct file *file, char *buf, loff_t size,
if (action & IMA_MEASURE) if (action & IMA_MEASURE)
ima_store_measurement(iint, file, pathname, ima_store_measurement(iint, file, pathname,
xattr_value, xattr_len, pcr); 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, rc = ima_appraise_measurement(func, iint, file, pathname,
xattr_value, xattr_len, opened); xattr_value, xattr_len, opened);
inode_unlock(inode);
}
if (action & IMA_AUDIT) if (action & IMA_AUDIT)
ima_audit_measurement(iint, pathname); ima_audit_measurement(iint, pathname);
if ((file->f_flags & O_DIRECT) && (iint->flags & IMA_PERMIT_DIRECTIO)) if ((file->f_flags & O_DIRECT) && (iint->flags & IMA_PERMIT_DIRECTIO))
rc = 0; rc = 0;
out_digsig: out_locked:
if ((mask & MAY_WRITE) && (iint->flags & IMA_DIGSIG) && if ((mask & MAY_WRITE) && test_bit(IMA_DIGSIG, &iint->atomic_flags) &&
!(iint->flags & IMA_NEW_FILE)) !(iint->flags & IMA_NEW_FILE))
rc = -EACCES; rc = -EACCES;
mutex_unlock(&iint->mutex);
kfree(xattr_value); kfree(xattr_value);
out_free: out:
if (pathbuf) if (pathbuf)
__putname(pathbuf); __putname(pathbuf);
out: if (must_appraise) {
inode_unlock(inode); if (rc && (ima_appraise & IMA_APPRAISE_ENFORCE))
if ((rc && must_appraise) && (ima_appraise & IMA_APPRAISE_ENFORCE)) return -EACCES;
return -EACCES; if (file->f_mode & FMODE_WRITE)
set_bit(IMA_UPDATE_XATTR, &iint->atomic_flags);
}
return 0; return 0;
} }
......
...@@ -29,11 +29,10 @@ ...@@ -29,11 +29,10 @@
/* iint cache flags */ /* iint cache flags */
#define IMA_ACTION_FLAGS 0xff000000 #define IMA_ACTION_FLAGS 0xff000000
#define IMA_ACTION_RULE_FLAGS 0x06000000 #define IMA_ACTION_RULE_FLAGS 0x06000000
#define IMA_DIGSIG 0x01000000 #define IMA_DIGSIG_REQUIRED 0x01000000
#define IMA_DIGSIG_REQUIRED 0x02000000 #define IMA_PERMIT_DIRECTIO 0x02000000
#define IMA_PERMIT_DIRECTIO 0x04000000 #define IMA_NEW_FILE 0x04000000
#define IMA_NEW_FILE 0x08000000 #define EVM_IMMUTABLE_DIGSIG 0x08000000
#define EVM_IMMUTABLE_DIGSIG 0x10000000
#define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \ #define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT | \
IMA_APPRAISE_SUBMASK) IMA_APPRAISE_SUBMASK)
...@@ -54,6 +53,13 @@ ...@@ -54,6 +53,13 @@
#define IMA_APPRAISED_SUBMASK (IMA_FILE_APPRAISED | IMA_MMAP_APPRAISED | \ #define IMA_APPRAISED_SUBMASK (IMA_FILE_APPRAISED | IMA_MMAP_APPRAISED | \
IMA_BPRM_APPRAISED | IMA_READ_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 { enum evm_ima_xattr_type {
IMA_XATTR_DIGEST = 0x01, IMA_XATTR_DIGEST = 0x01,
EVM_XATTR_HMAC, EVM_XATTR_HMAC,
...@@ -102,10 +108,12 @@ struct signature_v2_hdr { ...@@ -102,10 +108,12 @@ struct signature_v2_hdr {
/* integrity data associated with an inode */ /* integrity data associated with an inode */
struct integrity_iint_cache { struct integrity_iint_cache {
struct rb_node rb_node; /* rooted in integrity_iint_tree */ 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 */ struct inode *inode; /* back pointer to inode in question */
u64 version; /* track inode changes */ u64 version; /* track inode changes */
unsigned long flags; unsigned long flags;
unsigned long measured_pcrs; unsigned long measured_pcrs;
unsigned long atomic_flags;
enum integrity_status ima_file_status:4; enum integrity_status ima_file_status:4;
enum integrity_status ima_mmap_status:4; enum integrity_status ima_mmap_status:4;
enum integrity_status ima_bprm_status:4; enum integrity_status ima_bprm_status:4;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment