From 3b07e9ca26866697616097044f25fbe53dbab693 Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Mon, 20 Aug 2012 14:51:24 -0700 Subject: workqueue: deprecate system_nrt[_freezable]_wq system_nrt[_freezable]_wq are now spurious. Mark them deprecated and convert all users to system[_freezable]_wq. If you're cc'd and wondering what's going on: Now all workqueues are non-reentrant, so there's no reason to use system_nrt[_freezable]_wq. Please use system[_freezable]_wq instead. This patch doesn't make any functional difference. Signed-off-by: Tejun Heo Acked-By: Lai Jiangshan Cc: Jens Axboe Cc: David Airlie Cc: Jiri Kosina Cc: "David S. Miller" Cc: Rusty Russell Cc: "Paul E. McKenney" Cc: David Howells --- security/keys/gc.c | 8 ++++---- security/keys/key.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/keys/gc.c b/security/keys/gc.c index 61ab7c82ebb1..d67c97bb1025 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -62,7 +62,7 @@ void key_schedule_gc(time_t gc_at) if (gc_at <= now || test_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags)) { kdebug("IMMEDIATE"); - queue_work(system_nrt_wq, &key_gc_work); + schedule_work(&key_gc_work); } else if (gc_at < key_gc_next_run) { kdebug("DEFERRED"); key_gc_next_run = gc_at; @@ -77,7 +77,7 @@ void key_schedule_gc(time_t gc_at) void key_schedule_gc_links(void) { set_bit(KEY_GC_KEY_EXPIRED, &key_gc_flags); - queue_work(system_nrt_wq, &key_gc_work); + schedule_work(&key_gc_work); } /* @@ -120,7 +120,7 @@ void key_gc_keytype(struct key_type *ktype) set_bit(KEY_GC_REAP_KEYTYPE, &key_gc_flags); kdebug("schedule"); - queue_work(system_nrt_wq, &key_gc_work); + schedule_work(&key_gc_work); kdebug("sleep"); wait_on_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE, key_gc_wait_bit, @@ -369,7 +369,7 @@ maybe_resched: } if (gc_state & KEY_GC_REAP_AGAIN) - queue_work(system_nrt_wq, &key_gc_work); + schedule_work(&key_gc_work); kleave(" [end %x]", gc_state); return; diff --git a/security/keys/key.c b/security/keys/key.c index 50d96d4e06f2..3cbe3529c418 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -598,7 +598,7 @@ void key_put(struct key *key) key_check(key); if (atomic_dec_and_test(&key->usage)) - queue_work(system_nrt_wq, &key_gc_work); + schedule_work(&key_gc_work); } } EXPORT_SYMBOL(key_put); -- cgit v1.2.3 From 41ab999c80f1d368f32a2554ba8f44feff26f54d Mon Sep 17 00:00:00 2001 From: Kent Yoder Date: Thu, 7 Jun 2012 13:47:14 -0500 Subject: tpm: Move tpm_get_random api into the TPM device driver Move the tpm_get_random api from the trusted keys code into the TPM device driver itself so that other callers can make use of it. Also, change the api slightly so that the number of bytes read is returned in the call, since the TPM command can potentially return fewer bytes than requested. Acked-by: David Safford Reviewed-by: H. Peter Anvin Signed-off-by: Kent Yoder --- security/keys/trusted.c | 54 +++++++++++-------------------------------------- 1 file changed, 12 insertions(+), 42 deletions(-) (limited to 'security') diff --git a/security/keys/trusted.c b/security/keys/trusted.c index 2d5d041f2049..3f163d0489ad 100644 --- a/security/keys/trusted.c +++ b/security/keys/trusted.c @@ -368,38 +368,6 @@ static int trusted_tpm_send(const u32 chip_num, unsigned char *cmd, return rc; } -/* - * get a random value from TPM - */ -static int tpm_get_random(struct tpm_buf *tb, unsigned char *buf, uint32_t len) -{ - int ret; - - INIT_BUF(tb); - store16(tb, TPM_TAG_RQU_COMMAND); - store32(tb, TPM_GETRANDOM_SIZE); - store32(tb, TPM_ORD_GETRANDOM); - store32(tb, len); - ret = trusted_tpm_send(TPM_ANY_NUM, tb->data, sizeof tb->data); - if (!ret) - memcpy(buf, tb->data + TPM_GETRANDOM_SIZE, len); - return ret; -} - -static int my_get_random(unsigned char *buf, int len) -{ - struct tpm_buf *tb; - int ret; - - tb = kmalloc(sizeof *tb, GFP_KERNEL); - if (!tb) - return -ENOMEM; - ret = tpm_get_random(tb, buf, len); - - kfree(tb); - return ret; -} - /* * Lock a trusted key, by extending a selected PCR. * @@ -413,8 +381,8 @@ static int pcrlock(const int pcrnum) if (!capable(CAP_SYS_ADMIN)) return -EPERM; - ret = my_get_random(hash, SHA1_DIGEST_SIZE); - if (ret < 0) + ret = tpm_get_random(TPM_ANY_NUM, hash, SHA1_DIGEST_SIZE); + if (ret != SHA1_DIGEST_SIZE) return ret; return tpm_pcr_extend(TPM_ANY_NUM, pcrnum, hash) ? -EINVAL : 0; } @@ -429,8 +397,8 @@ static int osap(struct tpm_buf *tb, struct osapsess *s, unsigned char ononce[TPM_NONCE_SIZE]; int ret; - ret = tpm_get_random(tb, ononce, TPM_NONCE_SIZE); - if (ret < 0) + ret = tpm_get_random(TPM_ANY_NUM, ononce, TPM_NONCE_SIZE); + if (ret != TPM_NONCE_SIZE) return ret; INIT_BUF(tb); @@ -524,8 +492,8 @@ static int tpm_seal(struct tpm_buf *tb, uint16_t keytype, if (ret < 0) goto out; - ret = tpm_get_random(tb, td->nonceodd, TPM_NONCE_SIZE); - if (ret < 0) + ret = tpm_get_random(TPM_ANY_NUM, td->nonceodd, TPM_NONCE_SIZE); + if (ret != TPM_NONCE_SIZE) goto out; ordinal = htonl(TPM_ORD_SEAL); datsize = htonl(datalen); @@ -634,8 +602,8 @@ static int tpm_unseal(struct tpm_buf *tb, ordinal = htonl(TPM_ORD_UNSEAL); keyhndl = htonl(SRKHANDLE); - ret = tpm_get_random(tb, nonceodd, TPM_NONCE_SIZE); - if (ret < 0) { + ret = tpm_get_random(TPM_ANY_NUM, nonceodd, TPM_NONCE_SIZE); + if (ret != TPM_NONCE_SIZE) { pr_info("trusted_key: tpm_get_random failed (%d)\n", ret); return ret; } @@ -935,6 +903,7 @@ static int trusted_instantiate(struct key *key, const void *data, char *datablob; int ret = 0; int key_cmd; + size_t key_len; if (datalen <= 0 || datalen > 32767 || !data) return -EINVAL; @@ -974,8 +943,9 @@ static int trusted_instantiate(struct key *key, const void *data, pr_info("trusted_key: key_unseal failed (%d)\n", ret); break; case Opt_new: - ret = my_get_random(payload->key, payload->key_len); - if (ret < 0) { + key_len = payload->key_len; + ret = tpm_get_random(TPM_ANY_NUM, payload->key, key_len); + if (ret != key_len) { pr_info("trusted_key: key_create failed (%d)\n", ret); goto out; } -- cgit v1.2.3 From 20328b56cdf8fcc79f28c6c50ad8190fc0779e80 Mon Sep 17 00:00:00 2001 From: Kent Yoder Date: Wed, 22 Aug 2012 15:01:47 -0500 Subject: ima: enable the IBM vTPM as the default TPM in the PPC64 case Enable tpm_ibmvtpm driver by default when IMA is enabled on PPC64 Signed-off-by: Kent Yoder --- security/integrity/ima/Kconfig | 1 + 1 file changed, 1 insertion(+) (limited to 'security') diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index b9c1219924f1..809ccf19d09c 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -11,6 +11,7 @@ config IMA select CRYPTO_SHA1 select TCG_TPM if HAS_IOMEM && !UML select TCG_TIS if TCG_TPM && X86 + select TCG_IBMVTPM if TCG_TPM && PPC64 help The Trusted Computing Group(TCG) runtime Integrity Measurement Architecture(IMA) maintains a list of hash -- cgit v1.2.3 From c6993e4ac002c92bc75379212e9179c36d4bf7ee Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Tue, 4 Sep 2012 13:32:13 -0700 Subject: security: allow Yama to be unconditionally stacked Unconditionally call Yama when CONFIG_SECURITY_YAMA_STACKED is selected, no matter what LSM module is primary. Ubuntu and Chrome OS already carry patches to do this, and Fedora has voiced interest in doing this as well. Instead of having multiple distributions (or LSM authors) carrying these patches, just allow Yama to be called unconditionally when selected by the new CONFIG. Signed-off-by: Kees Cook Acked-by: Serge E. Hallyn Acked-by: Eric Paris Acked-by: John Johansen Signed-off-by: James Morris --- security/security.c | 21 +++++++++++++++++++++ security/yama/Kconfig | 8 ++++++++ security/yama/yama_lsm.c | 14 ++++++++++---- 3 files changed, 39 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/security.c b/security/security.c index 860aeb349cb3..68c1b9b45d93 100644 --- a/security/security.c +++ b/security/security.c @@ -136,11 +136,23 @@ int __init register_security(struct security_operations *ops) int security_ptrace_access_check(struct task_struct *child, unsigned int mode) { +#ifdef CONFIG_SECURITY_YAMA_STACKED + int rc; + rc = yama_ptrace_access_check(child, mode); + if (rc) + return rc; +#endif return security_ops->ptrace_access_check(child, mode); } int security_ptrace_traceme(struct task_struct *parent) { +#ifdef CONFIG_SECURITY_YAMA_STACKED + int rc; + rc = yama_ptrace_traceme(parent); + if (rc) + return rc; +#endif return security_ops->ptrace_traceme(parent); } @@ -761,6 +773,9 @@ int security_task_create(unsigned long clone_flags) void security_task_free(struct task_struct *task) { +#ifdef CONFIG_SECURITY_YAMA_STACKED + yama_task_free(task); +#endif security_ops->task_free(task); } @@ -876,6 +891,12 @@ int security_task_wait(struct task_struct *p) int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { +#ifdef CONFIG_SECURITY_YAMA_STACKED + int rc; + rc = yama_task_prctl(option, arg2, arg3, arg4, arg5); + if (rc != -ENOSYS) + return rc; +#endif return security_ops->task_prctl(option, arg2, arg3, arg4, arg5); } diff --git a/security/yama/Kconfig b/security/yama/Kconfig index 51d6709d8bbd..20ef5143c0c0 100644 --- a/security/yama/Kconfig +++ b/security/yama/Kconfig @@ -11,3 +11,11 @@ config SECURITY_YAMA Further information can be found in Documentation/security/Yama.txt. If you are unsure how to answer this question, answer N. + +config SECURITY_YAMA_STACKED + bool "Yama stacked with other LSMs" + depends on SECURITY_YAMA + default n + help + When Yama is built into the kernel, force it to stack with the + selected primary LSM. diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c index d51b7c76c37d..9ca43c1ea630 100644 --- a/security/yama/yama_lsm.c +++ b/security/yama/yama_lsm.c @@ -100,7 +100,7 @@ static void yama_ptracer_del(struct task_struct *tracer, * yama_task_free - check for task_pid to remove from exception list * @task: task being removed */ -static void yama_task_free(struct task_struct *task) +void yama_task_free(struct task_struct *task) { yama_ptracer_del(task, task); } @@ -116,7 +116,7 @@ static void yama_task_free(struct task_struct *task) * Return 0 on success, -ve on error. -ENOSYS is returned when Yama * does not handle the given option. */ -static int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, +int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { int rc; @@ -243,7 +243,7 @@ static int ptracer_exception_found(struct task_struct *tracer, * * Returns 0 if following the ptrace is allowed, -ve on error. */ -static int yama_ptrace_access_check(struct task_struct *child, +int yama_ptrace_access_check(struct task_struct *child, unsigned int mode) { int rc; @@ -296,7 +296,7 @@ static int yama_ptrace_access_check(struct task_struct *child, * * Returns 0 if following the ptrace is allowed, -ve on error. */ -static int yama_ptrace_traceme(struct task_struct *parent) +int yama_ptrace_traceme(struct task_struct *parent) { int rc; @@ -330,6 +330,7 @@ static int yama_ptrace_traceme(struct task_struct *parent) return rc; } +#ifndef CONFIG_SECURITY_YAMA_STACKED static struct security_operations yama_ops = { .name = "yama", @@ -338,6 +339,7 @@ static struct security_operations yama_ops = { .task_prctl = yama_task_prctl, .task_free = yama_task_free, }; +#endif #ifdef CONFIG_SYSCTL static int yama_dointvec_minmax(struct ctl_table *table, int write, @@ -384,13 +386,17 @@ static struct ctl_table yama_sysctl_table[] = { static __init int yama_init(void) { +#ifndef CONFIG_SECURITY_YAMA_STACKED if (!security_module_enable(&yama_ops)) return 0; +#endif printk(KERN_INFO "Yama: becoming mindful.\n"); +#ifndef CONFIG_SECURITY_YAMA_STACKED if (register_security(&yama_ops)) panic("Yama: kernel registration failed.\n"); +#endif #ifdef CONFIG_SYSCTL if (!register_sysctl_paths(yama_sysctl_path, yama_sysctl_table)) -- cgit v1.2.3 From 2e4930eb7c8fb20a39dfb5f8a8f80402710dcea8 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 27 Aug 2012 11:38:13 -0700 Subject: Yama: handle 32-bit userspace prctl When running a 64-bit kernel and receiving prctls from a 32-bit userspace, the "-1" used as an unsigned long will end up being misdetected. The kernel is looking for 0xffffffffffffffff instead of 0xffffffff. Since prctl lacks a distinct compat interface, Yama needs to handle this translation itself. As such, support either value as meaning PR_SET_PTRACER_ANY, to avoid breaking the ABI for 64-bit. Signed-off-by: Kees Cook Acked-by: John Johansen Cc: stable@vger.kernel.org Signed-off-by: James Morris --- security/yama/yama_lsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c index 9ca43c1ea630..01d3b44b62c1 100644 --- a/security/yama/yama_lsm.c +++ b/security/yama/yama_lsm.c @@ -143,7 +143,7 @@ int yama_task_prctl(int option, unsigned long arg2, unsigned long arg3, if (arg2 == 0) { yama_ptracer_del(NULL, myself); rc = 0; - } else if (arg2 == PR_SET_PTRACER_ANY) { + } else if (arg2 == PR_SET_PTRACER_ANY || (int)arg2 == -1) { rc = yama_ptracer_add(NULL, myself); } else { struct task_struct *tracer; -- cgit v1.2.3 From 2fe5d6def1672ae6635dd71867bf36dcfaa7434b Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 13 Feb 2012 10:15:05 -0500 Subject: ima: integrity appraisal extension IMA currently maintains an integrity measurement list used to assert the integrity of the running system to a third party. The IMA-appraisal extension adds local integrity validation and enforcement of the measurement against a "good" value stored as an extended attribute 'security.ima'. The initial methods for validating 'security.ima' are hashed based, which provides file data integrity, and digital signature based, which in addition to providing file data integrity, provides authenticity. This patch creates and maintains the 'security.ima' xattr, containing the file data hash measurement. Protection of the xattr is provided by EVM, if enabled and configured. Based on policy, IMA calls evm_verifyxattr() to verify a file's metadata integrity and, assuming success, compares the file's current hash value with the one stored as an extended attribute in 'security.ima'. Changelov v4: - changed iint cache flags to hex values Changelog v3: - change appraisal default for filesystems without xattr support to fail Changelog v2: - fix audit msg 'res' value - removed unused 'ima_appraise=' values Changelog v1: - removed unused iint mutex (Dmitry Kasatkin) - setattr hook must not reset appraised (Dmitry Kasatkin) - evm_verifyxattr() now differentiates between no 'security.evm' xattr (INTEGRITY_NOLABEL) and no EVM 'protected' xattrs included in the 'security.evm' (INTEGRITY_NOXATTRS). - replace hash_status with ima_status (Dmitry Kasatkin) - re-initialize slab element ima_status on free (Dmitry Kasatkin) - include 'security.ima' in EVM if CONFIG_IMA_APPRAISE, not CONFIG_IMA - merged half "ima: ima_must_appraise_or_measure API change" (Dmitry Kasatkin) - removed unnecessary error variable in process_measurement() (Dmitry Kasatkin) - use ima_inode_post_setattr() stub function, if IMA_APPRAISE not configured (moved ima_inode_post_setattr() to ima_appraise.c) - make sure ima_collect_measurement() can read file Changelog: - add 'iint' to evm_verifyxattr() call (Dimitry Kasatkin) - fix the race condition between chmod, which takes the i_mutex and then iint->mutex, and ima_file_free() and process_measurement(), which take the locks in the reverse order, by eliminating iint->mutex. (Dmitry Kasatkin) - cleanup of ima_appraise_measurement() (Dmitry Kasatkin) - changes as a result of the iint not allocated for all regular files, but only for those measured/appraised. - don't try to appraise new/empty files - expanded ima_appraisal description in ima/Kconfig - IMA appraise definitions required even if IMA_APPRAISE not enabled - add return value to ima_must_appraise() stub - unconditionally set status = INTEGRITY_PASS *after* testing status, not before. (Found by Joe Perches) Signed-off-by: Mimi Zohar Signed-off-by: Dmitry Kasatkin --- security/integrity/evm/evm_main.c | 3 + security/integrity/iint.c | 3 +- security/integrity/ima/Kconfig | 15 +++ security/integrity/ima/Makefile | 1 + security/integrity/ima/ima.h | 37 +++++++- security/integrity/ima/ima_api.c | 50 +++++++--- security/integrity/ima/ima_appraise.c | 168 ++++++++++++++++++++++++++++++++++ security/integrity/ima/ima_crypto.c | 8 +- security/integrity/ima/ima_main.c | 79 +++++++++++----- security/integrity/ima/ima_policy.c | 32 +++++-- security/integrity/integrity.h | 8 +- 11 files changed, 351 insertions(+), 53 deletions(-) create mode 100644 security/integrity/ima/ima_appraise.c (limited to 'security') diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 8901501425f4..eb5484504f50 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -33,6 +33,9 @@ char *evm_config_xattrnames[] = { #endif #ifdef CONFIG_SECURITY_SMACK XATTR_NAME_SMACK, +#endif +#ifdef CONFIG_IMA_APPRAISE + XATTR_NAME_IMA, #endif XATTR_NAME_CAPS, NULL diff --git a/security/integrity/iint.c b/security/integrity/iint.c index 399641c3e846..e600986aa49f 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -74,6 +74,7 @@ static void iint_free(struct integrity_iint_cache *iint) { iint->version = 0; iint->flags = 0UL; + iint->ima_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; kmem_cache_free(iint_cache, iint); } @@ -157,7 +158,7 @@ static void init_once(void *foo) memset(iint, 0, sizeof *iint); iint->version = 0; iint->flags = 0UL; - mutex_init(&iint->mutex); + iint->ima_status = INTEGRITY_UNKNOWN; iint->evm_status = INTEGRITY_UNKNOWN; } diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index 809ccf19d09c..d232c73647ae 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -56,3 +56,18 @@ config IMA_LSM_RULES default y help Disabling this option will disregard LSM based policy rules. + +config IMA_APPRAISE + bool "Appraise integrity measurements" + depends on IMA + default n + help + This option enables local measurement integrity appraisal. + It requires the system to be labeled with a security extended + attribute containing the file hash measurement. To protect + the security extended attributes from offline attack, enable + and configure EVM. + + For more information on integrity appraisal refer to: + + If unsure, say N. diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile index 5f740f6971e1..3f2ca6bdc384 100644 --- a/security/integrity/ima/Makefile +++ b/security/integrity/ima/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_IMA) += ima.o ima-y := ima_fs.o ima_queue.o ima_init.o ima_main.o ima_crypto.o ima_api.o \ ima_policy.o ima-$(CONFIG_IMA_AUDIT) += ima_audit.o +ima-$(CONFIG_IMA_APPRAISE) += ima_appraise.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index e7c99fd0d223..069a4aa63e95 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -40,6 +40,7 @@ enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; extern int ima_initialized; extern int ima_used_chip; extern char *ima_hash; +extern int ima_appraise; /* IMA inode template definition */ struct ima_template_data { @@ -107,6 +108,7 @@ static inline unsigned long ima_hash_key(u8 *digest) } /* LIM API function definitions */ +int ima_must_appraise_or_measure(struct inode *inode, int mask, int function); int ima_must_measure(struct inode *inode, int mask, int function); int ima_collect_measurement(struct integrity_iint_cache *iint, struct file *file); @@ -123,14 +125,45 @@ struct integrity_iint_cache *integrity_iint_insert(struct inode *inode); struct integrity_iint_cache *integrity_iint_find(struct inode *inode); /* IMA policy related functions */ -enum ima_hooks { FILE_CHECK = 1, FILE_MMAP, BPRM_CHECK }; +enum ima_hooks { FILE_CHECK = 1, FILE_MMAP, BPRM_CHECK, POST_SETATTR }; -int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask); +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, + int flags); void ima_init_policy(void); void ima_update_policy(void); ssize_t ima_parse_add_rule(char *); void ima_delete_rules(void); +/* Appraise integrity measurements */ +#define IMA_APPRAISE_ENFORCE 0x01 +#define IMA_APPRAISE_FIX 0x02 + +#ifdef CONFIG_IMA_APPRAISE +int ima_appraise_measurement(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename); +int ima_must_appraise(struct inode *inode, enum ima_hooks func, int mask); +void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file); + +#else +static inline int ima_appraise_measurement(struct integrity_iint_cache *iint, + struct file *file, + const unsigned char *filename) +{ + return INTEGRITY_UNKNOWN; +} + +static inline int ima_must_appraise(struct inode *inode, + enum ima_hooks func, int mask) +{ + return 0; +} + +static inline void ima_update_xattr(struct integrity_iint_cache *iint, + struct file *file) +{ +} +#endif + /* LSM based policy rules require audit */ #ifdef CONFIG_IMA_LSM_RULES diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 032ff03ad907..41cce84416c5 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -9,13 +9,17 @@ * License. * * File: ima_api.c - * Implements must_measure, collect_measurement, store_measurement, - * and store_template. + * Implements must_appraise_or_measure, collect_measurement, + * appraise_measurement, store_measurement and store_template. */ #include #include - +#include +#include +#include +#include #include "ima.h" + static const char *IMA_TEMPLATE_NAME = "ima"; /* @@ -93,7 +97,7 @@ err_out: } /** - * ima_must_measure - measure decision based on policy. + * ima_must_appraise_or_measure - appraise & measure decision based on policy. * @inode: pointer to inode to measure * @mask: contains the permission mask (MAY_READ, MAY_WRITE, MAY_EXECUTE) * @function: calling function (FILE_CHECK, BPRM_CHECK, FILE_MMAP) @@ -105,15 +109,22 @@ err_out: * mask: contains the permission mask * fsmagic: hex value * - * Return 0 to measure. For matching a DONT_MEASURE policy, no policy, - * or other error, return an error code. -*/ -int ima_must_measure(struct inode *inode, int mask, int function) + * Returns IMA_MEASURE, IMA_APPRAISE mask. + * + */ +int ima_must_appraise_or_measure(struct inode *inode, int mask, int function) { - int must_measure; + int flags = IMA_MEASURE | IMA_APPRAISE; + + if (!ima_appraise) + flags &= ~IMA_APPRAISE; + + return ima_match_policy(inode, function, mask, flags); +} - must_measure = ima_match_policy(inode, function, mask); - return must_measure ? 0 : -EACCES; +int ima_must_measure(struct inode *inode, int mask, int function) +{ + return ima_match_policy(inode, function, mask, IMA_MEASURE); } /* @@ -129,16 +140,24 @@ int ima_must_measure(struct inode *inode, int mask, int function) int ima_collect_measurement(struct integrity_iint_cache *iint, struct file *file) { - int result = -EEXIST; + struct inode *inode = file->f_dentry->d_inode; + const char *filename = file->f_dentry->d_name.name; + int result = 0; - if (!(iint->flags & IMA_MEASURED)) { + if (!(iint->flags & IMA_COLLECTED)) { u64 i_version = file->f_dentry->d_inode->i_version; memset(iint->digest, 0, IMA_DIGEST_SIZE); result = ima_calc_hash(file, iint->digest); - if (!result) + if (!result) { iint->version = i_version; + iint->flags |= IMA_COLLECTED; + } } + if (result) + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, + filename, "collect_data", "failed", + result, 0); return result; } @@ -167,6 +186,9 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct ima_template_entry *entry; int violation = 0; + if (iint->flags & IMA_MEASURED) + return; + entry = kmalloc(sizeof(*entry), GFP_KERNEL); if (!entry) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c new file mode 100644 index 000000000000..4865f61f9044 --- /dev/null +++ b/security/integrity/ima/ima_appraise.c @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2011 IBM Corporation + * + * Author: + * Mimi Zohar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "ima.h" + +static int __init default_appraise_setup(char *str) +{ + if (strncmp(str, "off", 3) == 0) + ima_appraise = 0; + else if (strncmp(str, "fix", 3) == 0) + ima_appraise = IMA_APPRAISE_FIX; + return 1; +} + +__setup("ima_appraise=", default_appraise_setup); + +/* + * ima_must_appraise - set appraise flag + * + * Return 1 to appraise + */ +int ima_must_appraise(struct inode *inode, enum ima_hooks func, int mask) +{ + return 0; +} + +static void ima_fix_xattr(struct dentry *dentry, + struct integrity_iint_cache *iint) +{ + iint->digest[0] = IMA_XATTR_DIGEST; + __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, + iint->digest, IMA_DIGEST_SIZE + 1, 0); +} + +/* + * ima_appraise_measurement - appraise file measurement + * + * Call evm_verifyxattr() to verify the integrity of 'security.ima'. + * Assuming success, compare the xattr hash with the collected measurement. + * + * Return 0 on success, error code otherwise + */ +int ima_appraise_measurement(struct integrity_iint_cache *iint, + struct file *file, const unsigned char *filename) +{ + struct dentry *dentry = file->f_dentry; + struct inode *inode = dentry->d_inode; + u8 xattr_value[IMA_DIGEST_SIZE]; + enum integrity_status status = INTEGRITY_UNKNOWN; + const char *op = "appraise_data"; + char *cause = "unknown"; + int rc; + + if (!ima_appraise) + return 0; + if (!inode->i_op->getxattr) + return INTEGRITY_UNKNOWN; + + if (iint->flags & IMA_APPRAISED) + return iint->ima_status; + + rc = inode->i_op->getxattr(dentry, XATTR_NAME_IMA, xattr_value, + IMA_DIGEST_SIZE); + if (rc <= 0) { + if (rc && rc != -ENODATA) + goto out; + + cause = "missing-hash"; + status = + (inode->i_size == 0) ? INTEGRITY_PASS : INTEGRITY_NOLABEL; + goto out; + } + + status = evm_verifyxattr(dentry, XATTR_NAME_IMA, xattr_value, rc, iint); + if ((status != INTEGRITY_PASS) && (status != INTEGRITY_UNKNOWN)) { + if ((status == INTEGRITY_NOLABEL) + || (status == INTEGRITY_NOXATTRS)) + cause = "missing-HMAC"; + else if (status == INTEGRITY_FAIL) + cause = "invalid-HMAC"; + goto out; + } + + rc = memcmp(xattr_value, iint->digest, IMA_DIGEST_SIZE); + if (rc) { + status = INTEGRITY_FAIL; + cause = "invalid-hash"; + print_hex_dump_bytes("security.ima: ", DUMP_PREFIX_NONE, + xattr_value, IMA_DIGEST_SIZE); + print_hex_dump_bytes("collected: ", DUMP_PREFIX_NONE, + iint->digest, IMA_DIGEST_SIZE); + goto out; + } + status = INTEGRITY_PASS; + iint->flags |= IMA_APPRAISED; +out: + if (status != INTEGRITY_PASS) { + if (ima_appraise & IMA_APPRAISE_FIX) { + ima_fix_xattr(dentry, iint); + status = INTEGRITY_PASS; + } + integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, filename, + op, cause, rc, 0); + } + iint->ima_status = status; + return status; +} + +/* + * ima_update_xattr - update 'security.ima' hash value + */ +void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) +{ + struct dentry *dentry = file->f_dentry; + int rc = 0; + + rc = ima_collect_measurement(iint, file); + if (rc < 0) + return; + ima_fix_xattr(dentry, iint); +} + +/** + * ima_inode_post_setattr - reflect file metadata changes + * @dentry: pointer to the affected dentry + * + * Changes to a dentry's metadata might result in needing to appraise. + * + * This function is called from notify_change(), which expects the caller + * to lock the inode's i_mutex. + */ +void ima_inode_post_setattr(struct dentry *dentry) +{ + struct inode *inode = dentry->d_inode; + struct integrity_iint_cache *iint; + int must_appraise, rc; + + if (!ima_initialized || !ima_appraise || !S_ISREG(inode->i_mode) + || !inode->i_op->removexattr) + return; + + must_appraise = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); + iint = integrity_iint_find(inode); + if (iint) { + if (must_appraise) + iint->flags |= IMA_APPRAISE; + else + iint->flags &= ~(IMA_APPRAISE | IMA_APPRAISED); + } + if (!must_appraise) + rc = inode->i_op->removexattr(dentry, XATTR_NAME_IMA); + return; +} diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 9b3ade7468b2..b21ee5b5495a 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -48,7 +48,7 @@ int ima_calc_hash(struct file *file, char *digest) struct scatterlist sg[1]; loff_t i_size, offset = 0; char *rbuf; - int rc; + int rc, read = 0; rc = init_desc(&desc); if (rc != 0) @@ -59,6 +59,10 @@ int ima_calc_hash(struct file *file, char *digest) rc = -ENOMEM; goto out; } + if (!(file->f_mode & FMODE_READ)) { + file->f_mode |= FMODE_READ; + read = 1; + } i_size = i_size_read(file->f_dentry->d_inode); while (offset < i_size) { int rbuf_len; @@ -80,6 +84,8 @@ int ima_calc_hash(struct file *file, char *digest) kfree(rbuf); if (!rc) rc = crypto_hash_final(&desc, digest); + if (read) + file->f_mode &= ~FMODE_READ; out: crypto_free_hash(desc.tfm); return rc; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index be8294915cf7..6eb28d47e74b 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -22,12 +22,19 @@ #include #include #include +#include #include #include "ima.h" int ima_initialized; +#ifdef CONFIG_IMA_APPRAISE +int ima_appraise = IMA_APPRAISE_ENFORCE; +#else +int ima_appraise; +#endif + char *ima_hash = "sha1"; static int __init hash_setup(char *str) { @@ -52,7 +59,7 @@ static void ima_rdwr_violation_check(struct file *file) struct dentry *dentry = file->f_path.dentry; struct inode *inode = dentry->d_inode; fmode_t mode = file->f_mode; - int rc; + int must_measure; bool send_tomtou = false, send_writers = false; unsigned char *pathname = NULL, *pathbuf = NULL; @@ -67,8 +74,8 @@ static void ima_rdwr_violation_check(struct file *file) goto out; } - rc = ima_must_measure(inode, MAY_READ, FILE_CHECK); - if (rc < 0) + must_measure = ima_must_measure(inode, MAY_READ, FILE_CHECK); + if (!must_measure) goto out; if (atomic_read(&inode->i_writecount) > 0) @@ -100,17 +107,21 @@ out: } 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; - mutex_lock(&iint->mutex); - if (mode & FMODE_WRITE && - atomic_read(&inode->i_writecount) == 1 && - iint->version != inode->i_version) - iint->flags &= ~IMA_MEASURED; - mutex_unlock(&iint->mutex); + if (!(mode & FMODE_WRITE)) + return; + + mutex_lock(&inode->i_mutex); + if (atomic_read(&inode->i_writecount) == 1 && + iint->version != inode->i_version) { + iint->flags &= ~(IMA_COLLECTED | IMA_APPRAISED | IMA_MEASURED); + if (iint->flags & IMA_APPRAISE) + ima_update_xattr(iint, file); + } + mutex_unlock(&inode->i_mutex); } /** @@ -140,14 +151,17 @@ static int process_measurement(struct file *file, const unsigned char *filename, struct inode *inode = file->f_dentry->d_inode; struct integrity_iint_cache *iint; unsigned char *pathname = NULL, *pathbuf = NULL; - int rc = 0; + int rc = -ENOMEM, action, must_appraise; if (!ima_initialized || !S_ISREG(inode->i_mode)) return 0; - rc = ima_must_measure(inode, mask, function); - if (rc != 0) - return rc; + /* Determine if in appraise/measurement policy, + * returns IMA_MEASURE, IMA_APPRAISE bitmask. */ + action = ima_must_appraise_or_measure(inode, mask, function); + if (!action) + return 0; + retry: iint = integrity_iint_find(inode); if (!iint) { @@ -157,11 +171,21 @@ retry: return rc; } - mutex_lock(&iint->mutex); + must_appraise = action & IMA_APPRAISE; - rc = iint->flags & IMA_MEASURED ? 1 : 0; - if (rc != 0) + mutex_lock(&inode->i_mutex); + + /* Determine if already appraised/measured based on bitmask + * (IMA_MEASURE, IMA_MEASURED, IMA_APPRAISE, IMA_APPRAISED) */ + iint->flags |= action; + action &= ~((iint->flags & (IMA_MEASURED | IMA_APPRAISED)) >> 1); + + /* Nothing to do, just return existing appraised status */ + if (!action) { + if (iint->flags & IMA_APPRAISED) + rc = iint->ima_status; goto out; + } rc = ima_collect_measurement(iint, file); if (rc != 0) @@ -177,11 +201,16 @@ retry: pathname = NULL; } } - ima_store_measurement(iint, file, !pathname ? filename : pathname); + if (action & IMA_MEASURE) + ima_store_measurement(iint, file, + !pathname ? filename : pathname); + if (action & IMA_APPRAISE) + rc = ima_appraise_measurement(iint, file, + !pathname ? filename : pathname); kfree(pathbuf); out: - mutex_unlock(&iint->mutex); - return rc; + mutex_unlock(&inode->i_mutex); + return (rc && must_appraise) ? -EACCES : 0; } /** @@ -197,14 +226,14 @@ out: */ int ima_file_mmap(struct file *file, unsigned long prot) { - int rc; + int rc = 0; if (!file) return 0; if (prot & PROT_EXEC) rc = process_measurement(file, file->f_dentry->d_name.name, MAY_EXEC, FILE_MMAP); - return 0; + return (ima_appraise & IMA_APPRAISE_ENFORCE) ? rc : 0; } /** @@ -228,7 +257,7 @@ int ima_bprm_check(struct linux_binprm *bprm) (strcmp(bprm->filename, bprm->interp) == 0) ? bprm->filename : bprm->interp, MAY_EXEC, BPRM_CHECK); - return 0; + return (ima_appraise & IMA_APPRAISE_ENFORCE) ? rc : 0; } /** @@ -249,7 +278,7 @@ int ima_file_check(struct file *file, int mask) rc = process_measurement(file, file->f_dentry->d_name.name, mask & (MAY_READ | MAY_WRITE | MAY_EXEC), FILE_CHECK); - return 0; + return (ima_appraise & IMA_APPRAISE_ENFORCE) ? rc : 0; } EXPORT_SYMBOL_GPL(ima_file_check); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 1a9583008aae..3e22e17da295 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -25,7 +25,13 @@ #define IMA_FSMAGIC 0x0004 #define IMA_UID 0x0008 -enum ima_action { UNKNOWN = -1, DONT_MEASURE = 0, MEASURE }; +#define UNKNOWN 0 +#define MEASURE 1 /* same as IMA_MEASURE */ +#define DONT_MEASURE 2 +#define MEASURE_MASK 3 +#define APPRAISE 4 /* same as IMA_APPRAISE */ +#define DONT_APPRAISE 8 +#define APPRAISE_MASK 12 #define MAX_LSM_RULES 6 enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, @@ -34,7 +40,7 @@ enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, struct ima_measure_rule_entry { struct list_head list; - enum ima_action action; + int action; unsigned int flags; enum ima_hooks func; int mask; @@ -163,18 +169,28 @@ static bool ima_match_rules(struct ima_measure_rule_entry *rule, * as elements in the list are never deleted, nor does the list * change.) */ -int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask) +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, + int flags) { struct ima_measure_rule_entry *entry; + int action = 0, actmask = flags | (flags << 1); list_for_each_entry(entry, ima_measure, list) { - bool rc; - rc = ima_match_rules(entry, inode, func, mask); - if (rc) - return entry->action; + if (!(entry->action & actmask)) + continue; + + if (!ima_match_rules(entry, inode, func, mask)) + continue; + + action |= (entry->action & (IMA_APPRAISE | IMA_MEASURE)); + actmask &= (entry->action & APPRAISE_MASK) ? + ~APPRAISE_MASK : ~MEASURE_MASK; + if (!actmask) + break; } - return 0; + + return action; } /** diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 7a25ecec5aaa..dac6b68e945a 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -16,7 +16,11 @@ #include /* iint cache flags */ -#define IMA_MEASURED 0x01 +#define IMA_MEASURE 0x01 +#define IMA_MEASURED 0x02 +#define IMA_APPRAISE 0x04 +#define IMA_APPRAISED 0x08 +#define IMA_COLLECTED 0x10 enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, @@ -36,7 +40,7 @@ struct integrity_iint_cache { u64 version; /* track inode changes */ unsigned char flags; u8 digest[SHA1_DIGEST_SIZE]; - struct mutex mutex; /* protects: version, flags, digest */ + enum integrity_status ima_status; enum integrity_status evm_status; }; -- cgit v1.2.3 From 07f6a79415d7d502ee0c7d02ace6594a7be7429a Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Wed, 9 Mar 2011 22:25:48 -0500 Subject: ima: add appraise action keywords and default rules Unlike the IMA measurement policy, the appraise policy can not be dependent on runtime process information, such as the task uid, as the 'security.ima' xattr is written on file close and must be updated each time the file changes, regardless of the current task uid. This patch extends the policy language with 'fowner', defines an appraise policy, which appraises all files owned by root, and defines 'ima_appraise_tcb', a new boot command line option, to enable the appraise policy. Changelog v3: - separate the measure from the appraise rules in order to support measuring without appraising and appraising without measuring. - change appraisal default for filesystems without xattr support to fail - update default appraise policy for cgroups Changelog v1: - don't appraise RAMFS (Dmitry Kasatkin) - merged rest of "ima: ima_must_appraise_or_measure API change" commit (Dmtiry Kasatkin) ima_must_appraise_or_measure() called ima_match_policy twice, which searched the policy for a matching rule. Once for a matching measurement rule and subsequently for an appraisal rule. Searching the policy twice is unnecessary overhead, which could be noticeable with a large policy. The new version of ima_must_appraise_or_measure() does everything in a single iteration using a new version of ima_match_policy(). It returns IMA_MEASURE, IMA_APPRAISE mask. With the use of action mask only one efficient matching function is enough. Removed other specific versions of matching functions. Changelog: - change 'owner' to 'fowner' to conform to the new LSM conditions posted by Roberto Sassu. - fix calls to ima_log_string() Signed-off-by: Mimi Zohar Signed-off-by: Dmitry Kasatkin --- security/integrity/ima/ima_appraise.c | 5 +- security/integrity/ima/ima_policy.c | 151 +++++++++++++++++++++++++--------- 2 files changed, 116 insertions(+), 40 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 4865f61f9044..681cb6e72257 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -36,7 +36,10 @@ __setup("ima_appraise=", default_appraise_setup); */ int ima_must_appraise(struct inode *inode, enum ima_hooks func, int mask) { - return 0; + if (!ima_appraise) + return 0; + + return ima_match_policy(inode, func, mask, IMA_APPRAISE); } static void ima_fix_xattr(struct dentry *dentry, diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 3e22e17da295..0d6d60b4ba6f 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -24,6 +24,7 @@ #define IMA_MASK 0x0002 #define IMA_FSMAGIC 0x0004 #define IMA_UID 0x0008 +#define IMA_FOWNER 0x0010 #define UNKNOWN 0 #define MEASURE 1 /* same as IMA_MEASURE */ @@ -38,7 +39,7 @@ enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, LSM_SUBJ_USER, LSM_SUBJ_ROLE, LSM_SUBJ_TYPE }; -struct ima_measure_rule_entry { +struct ima_rule_entry { struct list_head list; int action; unsigned int flags; @@ -46,6 +47,7 @@ struct ima_measure_rule_entry { int mask; unsigned long fsmagic; uid_t uid; + uid_t fowner; struct { void *rule; /* LSM file metadata specific */ int type; /* audit type */ @@ -54,7 +56,7 @@ struct ima_measure_rule_entry { /* * Without LSM specific knowledge, the default policy can only be - * written in terms of .action, .func, .mask, .fsmagic, and .uid + * written in terms of .action, .func, .mask, .fsmagic, .uid, and .fowner */ /* @@ -63,7 +65,7 @@ struct ima_measure_rule_entry { * normal users can easily run the machine out of memory simply building * and running executables. */ -static struct ima_measure_rule_entry default_rules[] = { +static struct ima_rule_entry default_rules[] = { {.action = DONT_MEASURE,.fsmagic = PROC_SUPER_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC}, {.action = DONT_MEASURE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC}, @@ -81,19 +83,41 @@ static struct ima_measure_rule_entry default_rules[] = { .flags = IMA_FUNC | IMA_MASK | IMA_UID}, }; -static LIST_HEAD(measure_default_rules); -static LIST_HEAD(measure_policy_rules); -static struct list_head *ima_measure; +static struct ima_rule_entry default_appraise_rules[] = { + {.action = DONT_APPRAISE,.fsmagic = PROC_SUPER_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = SYSFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = DEBUGFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = RAMFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = DEVPTS_SUPER_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = BINFMTFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = SECURITYFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = SELINUX_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE,.fsmagic = CGROUP_SUPER_MAGIC,.flags = IMA_FSMAGIC}, + {.action = APPRAISE,.fowner = 0,.flags = IMA_FOWNER}, +}; + +static LIST_HEAD(ima_default_rules); +static LIST_HEAD(ima_policy_rules); +static struct list_head *ima_rules; -static DEFINE_MUTEX(ima_measure_mutex); +static DEFINE_MUTEX(ima_rules_mutex); static bool ima_use_tcb __initdata; -static int __init default_policy_setup(char *str) +static int __init default_measure_policy_setup(char *str) { ima_use_tcb = 1; return 1; } -__setup("ima_tcb", default_policy_setup); +__setup("ima_tcb", default_measure_policy_setup); + +static bool ima_use_appraise_tcb __initdata; +static int __init default_appraise_policy_setup(char *str) +{ + ima_use_appraise_tcb = 1; + return 1; +} +__setup("ima_appraise_tcb", default_appraise_policy_setup); /** * ima_match_rules - determine whether an inode matches the measure rule. @@ -104,7 +128,7 @@ __setup("ima_tcb", default_policy_setup); * * Returns true on rule match, false on failure. */ -static bool ima_match_rules(struct ima_measure_rule_entry *rule, +static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode, enum ima_hooks func, int mask) { struct task_struct *tsk = current; @@ -120,6 +144,8 @@ static bool ima_match_rules(struct ima_measure_rule_entry *rule, return false; if ((rule->flags & IMA_UID) && rule->uid != cred->uid) return false; + if ((rule->flags & IMA_FOWNER) && rule->fowner != inode->i_uid) + return false; for (i = 0; i < MAX_LSM_RULES; i++) { int rc = 0; u32 osid, sid; @@ -172,10 +198,10 @@ static bool ima_match_rules(struct ima_measure_rule_entry *rule, int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, int flags) { - struct ima_measure_rule_entry *entry; + struct ima_rule_entry *entry; int action = 0, actmask = flags | (flags << 1); - list_for_each_entry(entry, ima_measure, list) { + list_for_each_entry(entry, ima_rules, list) { if (!(entry->action & actmask)) continue; @@ -196,22 +222,31 @@ int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, /** * ima_init_policy - initialize the default measure rules. * - * ima_measure points to either the measure_default_rules or the - * the new measure_policy_rules. + * ima_rules points to either the ima_default_rules or the + * the new ima_policy_rules. */ void __init ima_init_policy(void) { - int i, entries; + int i, measure_entries, appraise_entries; /* if !ima_use_tcb set entries = 0 so we load NO default rules */ - if (ima_use_tcb) - entries = ARRAY_SIZE(default_rules); - else - entries = 0; - - for (i = 0; i < entries; i++) - list_add_tail(&default_rules[i].list, &measure_default_rules); - ima_measure = &measure_default_rules; + measure_entries = ima_use_tcb ? ARRAY_SIZE(default_rules) : 0; + appraise_entries = ima_use_appraise_tcb ? + ARRAY_SIZE(default_appraise_rules) : 0; + + for (i = 0; i < measure_entries + appraise_entries; i++) { + if (i < measure_entries) + list_add_tail(&default_rules[i].list, + &ima_default_rules); + else { + int j = i - measure_entries; + + list_add_tail(&default_appraise_rules[j].list, + &ima_default_rules); + } + } + + ima_rules = &ima_default_rules; } /** @@ -228,8 +263,8 @@ void ima_update_policy(void) int result = 1; int audit_info = 0; - if (ima_measure == &measure_default_rules) { - ima_measure = &measure_policy_rules; + if (ima_rules == &ima_default_rules) { + ima_rules = &ima_policy_rules; cause = "complete"; result = 0; } @@ -240,14 +275,17 @@ void ima_update_policy(void) enum { Opt_err = -1, Opt_measure = 1, Opt_dont_measure, + Opt_appraise, Opt_dont_appraise, Opt_obj_user, Opt_obj_role, Opt_obj_type, Opt_subj_user, Opt_subj_role, Opt_subj_type, - Opt_func, Opt_mask, Opt_fsmagic, Opt_uid + Opt_func, Opt_mask, Opt_fsmagic, Opt_uid, Opt_fowner }; static match_table_t policy_tokens = { {Opt_measure, "measure"}, {Opt_dont_measure, "dont_measure"}, + {Opt_appraise, "appraise"}, + {Opt_dont_appraise, "dont_appraise"}, {Opt_obj_user, "obj_user=%s"}, {Opt_obj_role, "obj_role=%s"}, {Opt_obj_type, "obj_type=%s"}, @@ -258,10 +296,11 @@ static match_table_t policy_tokens = { {Opt_mask, "mask=%s"}, {Opt_fsmagic, "fsmagic=%s"}, {Opt_uid, "uid=%s"}, + {Opt_fowner, "fowner=%s"}, {Opt_err, NULL} }; -static int ima_lsm_rule_init(struct ima_measure_rule_entry *entry, +static int ima_lsm_rule_init(struct ima_rule_entry *entry, char *args, int lsm_rule, int audit_type) { int result; @@ -285,7 +324,7 @@ static void ima_log_string(struct audit_buffer *ab, char *key, char *value) audit_log_format(ab, " "); } -static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) +static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) { struct audit_buffer *ab; char *p; @@ -294,6 +333,7 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_INTEGRITY_RULE); entry->uid = -1; + entry->fowner = -1; entry->action = UNKNOWN; while ((p = strsep(&rule, " \t")) != NULL) { substring_t args[MAX_OPT_ARGS]; @@ -322,11 +362,27 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) entry->action = DONT_MEASURE; break; + case Opt_appraise: + ima_log_string(ab, "action", "appraise"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = APPRAISE; + break; + case Opt_dont_appraise: + ima_log_string(ab, "action", "dont_appraise"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = DONT_APPRAISE; + break; case Opt_func: ima_log_string(ab, "func", args[0].from); if (entry->func) - result = -EINVAL; + result = -EINVAL; if (strcmp(args[0].from, "FILE_CHECK") == 0) entry->func = FILE_CHECK; @@ -391,6 +447,23 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) entry->flags |= IMA_UID; } break; + case Opt_fowner: + ima_log_string(ab, "fowner", args[0].from); + + if (entry->fowner != -1) { + result = -EINVAL; + break; + } + + result = strict_strtoul(args[0].from, 10, &lnum); + if (!result) { + entry->fowner = (uid_t) lnum; + if (entry->fowner != lnum) + result = -EINVAL; + else + entry->flags |= IMA_FOWNER; + } + break; case Opt_obj_user: ima_log_string(ab, "obj_user", args[0].from); result = ima_lsm_rule_init(entry, args[0].from, @@ -442,7 +515,7 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) } /** - * ima_parse_add_rule - add a rule to measure_policy_rules + * ima_parse_add_rule - add a rule to ima_policy_rules * @rule - ima measurement policy rule * * Uses a mutex to protect the policy list from multiple concurrent writers. @@ -452,12 +525,12 @@ ssize_t ima_parse_add_rule(char *rule) { const char *op = "update_policy"; char *p; - struct ima_measure_rule_entry *entry; + struct ima_rule_entry *entry; ssize_t result, len; int audit_info = 0; /* Prevent installed policy from changing */ - if (ima_measure != &measure_default_rules) { + if (ima_rules != &ima_default_rules) { integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, op, "already exists", -EACCES, audit_info); @@ -490,9 +563,9 @@ ssize_t ima_parse_add_rule(char *rule) return result; } - mutex_lock(&ima_measure_mutex); - list_add_tail(&entry->list, &measure_policy_rules); - mutex_unlock(&ima_measure_mutex); + mutex_lock(&ima_rules_mutex); + list_add_tail(&entry->list, &ima_policy_rules); + mutex_unlock(&ima_rules_mutex); return len; } @@ -500,12 +573,12 @@ ssize_t ima_parse_add_rule(char *rule) /* ima_delete_rules called to cleanup invalid policy */ void ima_delete_rules(void) { - struct ima_measure_rule_entry *entry, *tmp; + struct ima_rule_entry *entry, *tmp; - mutex_lock(&ima_measure_mutex); - list_for_each_entry_safe(entry, tmp, &measure_policy_rules, list) { + mutex_lock(&ima_rules_mutex); + list_for_each_entry_safe(entry, tmp, &ima_policy_rules, list) { list_del(&entry->list); kfree(entry); } - mutex_unlock(&ima_measure_mutex); + mutex_unlock(&ima_rules_mutex); } -- cgit v1.2.3 From bf2276d10ce58ff44ab8857266a6718024496af6 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Wed, 19 Oct 2011 12:04:40 +0300 Subject: ima: allocating iint improvements With IMA-appraisal's removal of the iint mutex and taking the i_mutex instead, allocating the iint becomes a lot simplier, as we don't need to be concerned with two processes racing to allocate the iint. This patch cleans up and improves performance for allocating the iint. - removed redundant double i_mutex locking - combined iint allocation with tree search Changelog v2: - removed the rwlock/read_lock changes from this patch Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/iint.c | 45 +++++++++++++++++---------------------- security/integrity/ima/ima_main.c | 13 ++++------- 2 files changed, 23 insertions(+), 35 deletions(-) (limited to 'security') diff --git a/security/integrity/iint.c b/security/integrity/iint.c index e600986aa49f..c91a436e13ac 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -80,24 +80,26 @@ static void iint_free(struct integrity_iint_cache *iint) } /** - * integrity_inode_alloc - allocate an iint associated with an inode + * integrity_inode_get - find or allocate an iint associated with an inode * @inode: pointer to the inode + * @return: allocated iint + * + * Caller must lock i_mutex */ -int integrity_inode_alloc(struct inode *inode) +struct integrity_iint_cache *integrity_inode_get(struct inode *inode) { struct rb_node **p; - struct rb_node *new_node, *parent = NULL; - struct integrity_iint_cache *new_iint, *test_iint; - int rc; + struct rb_node *node, *parent = NULL; + struct integrity_iint_cache *iint, *test_iint; - new_iint = kmem_cache_alloc(iint_cache, GFP_NOFS); - if (!new_iint) - return -ENOMEM; + iint = integrity_iint_find(inode); + if (iint) + return iint; - new_iint->inode = inode; - new_node = &new_iint->rb_node; + iint = kmem_cache_alloc(iint_cache, GFP_NOFS); + if (!iint) + return NULL; - mutex_lock(&inode->i_mutex); /* i_flags */ spin_lock(&integrity_iint_lock); p = &integrity_iint_tree.rb_node; @@ -105,29 +107,20 @@ int integrity_inode_alloc(struct inode *inode) parent = *p; test_iint = rb_entry(parent, struct integrity_iint_cache, rb_node); - rc = -EEXIST; if (inode < test_iint->inode) p = &(*p)->rb_left; - else if (inode > test_iint->inode) - p = &(*p)->rb_right; else - goto out_err; + p = &(*p)->rb_right; } + iint->inode = inode; + node = &iint->rb_node; inode->i_flags |= S_IMA; - rb_link_node(new_node, parent, p); - rb_insert_color(new_node, &integrity_iint_tree); + rb_link_node(node, parent, p); + rb_insert_color(node, &integrity_iint_tree); spin_unlock(&integrity_iint_lock); - mutex_unlock(&inode->i_mutex); /* i_flags */ - - return 0; -out_err: - spin_unlock(&integrity_iint_lock); - mutex_unlock(&inode->i_mutex); /* i_flags */ - iint_free(new_iint); - - return rc; + return iint; } /** diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 6eb28d47e74b..df6521296051 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -162,19 +162,14 @@ static int process_measurement(struct file *file, const unsigned char *filename, if (!action) return 0; -retry: - iint = integrity_iint_find(inode); - if (!iint) { - rc = integrity_inode_alloc(inode); - if (!rc || rc == -EEXIST) - goto retry; - return rc; - } - must_appraise = action & IMA_APPRAISE; mutex_lock(&inode->i_mutex); + iint = integrity_inode_get(inode); + if (!iint) + goto out; + /* Determine if already appraised/measured based on bitmask * (IMA_MEASURE, IMA_MEASURED, IMA_APPRAISE, IMA_APPRAISED) */ iint->flags |= action; -- cgit v1.2.3 From a10bf26b2f53242836e9362c6c9c857b627b82a9 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Wed, 8 Feb 2012 14:15:42 -0500 Subject: ima: replace iint spinblock with rwlock/read_lock For performance, replace the iint spinlock with rwlock/read_lock. Eric Paris questioned this change, from spinlocks to rwlocks, saying "rwlocks have been shown to actually be slower on multi processor systems in a number of cases due to the cache line bouncing required." Based on performance measurements compiling the kernel on a cold boot with multiple jobs with/without this patch, Dmitry Kasatkin and I found that rwlocks performed better than spinlocks, but very insignificantly. For example with total compilation time around 6 minutes, with rwlocks time was 1 - 3 seconds shorter... but always like that. Changelog v2: - new patch taken from the 'allocating iint improvements' patch Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/iint.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'security') diff --git a/security/integrity/iint.c b/security/integrity/iint.c index c91a436e13ac..d82a5a13d855 100644 --- a/security/integrity/iint.c +++ b/security/integrity/iint.c @@ -22,7 +22,7 @@ #include "integrity.h" static struct rb_root integrity_iint_tree = RB_ROOT; -static DEFINE_SPINLOCK(integrity_iint_lock); +static DEFINE_RWLOCK(integrity_iint_lock); static struct kmem_cache *iint_cache __read_mostly; int iint_initialized; @@ -35,8 +35,6 @@ static struct integrity_iint_cache *__integrity_iint_find(struct inode *inode) struct integrity_iint_cache *iint; struct rb_node *n = integrity_iint_tree.rb_node; - assert_spin_locked(&integrity_iint_lock); - while (n) { iint = rb_entry(n, struct integrity_iint_cache, rb_node); @@ -63,9 +61,9 @@ struct integrity_iint_cache *integrity_iint_find(struct inode *inode) if (!IS_IMA(inode)) return NULL; - spin_lock(&integrity_iint_lock); + read_lock(&integrity_iint_lock); iint = __integrity_iint_find(inode); - spin_unlock(&integrity_iint_lock); + read_unlock(&integrity_iint_lock); return iint; } @@ -100,7 +98,7 @@ struct integrity_iint_cache *integrity_inode_get(struct inode *inode) if (!iint) return NULL; - spin_lock(&integrity_iint_lock); + write_lock(&integrity_iint_lock); p = &integrity_iint_tree.rb_node; while (*p) { @@ -119,7 +117,7 @@ struct integrity_iint_cache *integrity_inode_get(struct inode *inode) rb_link_node(node, parent, p); rb_insert_color(node, &integrity_iint_tree); - spin_unlock(&integrity_iint_lock); + write_unlock(&integrity_iint_lock); return iint; } @@ -136,10 +134,10 @@ void integrity_inode_free(struct inode *inode) if (!IS_IMA(inode)) return; - spin_lock(&integrity_iint_lock); + write_lock(&integrity_iint_lock); iint = __integrity_iint_find(inode); rb_erase(&iint->rb_node, &integrity_iint_tree); - spin_unlock(&integrity_iint_lock); + write_unlock(&integrity_iint_lock); iint_free(iint); } -- cgit v1.2.3 From 42c63330f2b05aa6077c1bfc2798c04afe54f6b2 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Thu, 10 Mar 2011 18:54:15 -0500 Subject: ima: add ima_inode_setxattr/removexattr function and calls Based on xattr_permission comments, the restriction to modify 'security' xattr is left up to the underlying fs or lsm. Ensure that not just anyone can modify or remove 'security.ima'. Changelog v1: - Unless IMA-APPRAISE is configured, use stub ima_inode_removexattr()/setxattr() functions. (Moved ima_inode_removexattr()/setxattr() to ima_appraise.c) Changelog: - take i_mutex to fix locking (Dmitry Kasatkin) - ima_reset_appraise_flags should only be called when modifying or removing the 'security.ima' xattr. Requires CAP_SYS_ADMIN privilege. (Incorporated fix from Roberto Sassu) - Even if allowed to update security.ima, reset the appraisal flags, forcing re-appraisal. - Replace CAP_MAC_ADMIN with CAP_SYS_ADMIN - static inline ima_inode_setxattr()/ima_inode_removexattr() stubs - ima_protect_xattr should be static Signed-off-by: Mimi Zohar Signed-off-by: Dmitry Kasatkin --- security/integrity/ima/ima_appraise.c | 57 +++++++++++++++++++++++++++++++++++ security/security.c | 6 ++++ 2 files changed, 63 insertions(+) (limited to 'security') diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 681cb6e72257..becc7e09116d 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -169,3 +169,60 @@ void ima_inode_post_setattr(struct dentry *dentry) rc = inode->i_op->removexattr(dentry, XATTR_NAME_IMA); return; } + +/* + * ima_protect_xattr - protect 'security.ima' + * + * Ensure that not just anyone can modify or remove 'security.ima'. + */ +static int ima_protect_xattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + if (strcmp(xattr_name, XATTR_NAME_IMA) == 0) { + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + return 1; + } + return 0; +} + +static void ima_reset_appraise_flags(struct inode *inode) +{ + struct integrity_iint_cache *iint; + + if (!ima_initialized || !ima_appraise || !S_ISREG(inode->i_mode)) + return; + + iint = integrity_iint_find(inode); + if (!iint) + return; + + iint->flags &= ~(IMA_COLLECTED | IMA_APPRAISED | IMA_MEASURED); + return; +} + +int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name, + const void *xattr_value, size_t xattr_value_len) +{ + int result; + + result = ima_protect_xattr(dentry, xattr_name, xattr_value, + xattr_value_len); + if (result == 1) { + ima_reset_appraise_flags(dentry->d_inode); + result = 0; + } + return result; +} + +int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name) +{ + int result; + + result = ima_protect_xattr(dentry, xattr_name, NULL, 0); + if (result == 1) { + ima_reset_appraise_flags(dentry->d_inode); + result = 0; + } + return result; +} diff --git a/security/security.c b/security/security.c index 68c1b9b45d93..d23b43522a5a 100644 --- a/security/security.c +++ b/security/security.c @@ -571,6 +571,9 @@ int security_inode_setxattr(struct dentry *dentry, const char *name, if (unlikely(IS_PRIVATE(dentry->d_inode))) return 0; ret = security_ops->inode_setxattr(dentry, name, value, size, flags); + if (ret) + return ret; + ret = ima_inode_setxattr(dentry, name, value, size); if (ret) return ret; return evm_inode_setxattr(dentry, name, value, size); @@ -606,6 +609,9 @@ int security_inode_removexattr(struct dentry *dentry, const char *name) if (unlikely(IS_PRIVATE(dentry->d_inode))) return 0; ret = security_ops->inode_removexattr(dentry, name); + if (ret) + return ret; + ret = ima_inode_removexattr(dentry, name); if (ret) return ret; return evm_inode_removexattr(dentry, name); -- cgit v1.2.3 From 5a44b41207174e1882ce0c24a752f4cfb65dab07 Mon Sep 17 00:00:00 2001 From: Mimi Zohar Date: Mon, 9 Jan 2012 22:59:36 -0500 Subject: ima: add support for different security.ima data types IMA-appraisal currently verifies the integrity of a file based on a known 'good' measurement value. This patch reserves the first byte of 'security.ima' as a place holder for the type of method used for verifying file data integrity. Changelog v1: - Use the newly defined 'struct evm_ima_xattr_data' Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_api.c | 6 +++--- security/integrity/ima/ima_appraise.c | 23 +++++++++++++---------- security/integrity/integrity.h | 2 +- 3 files changed, 17 insertions(+), 14 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 41cce84416c5..33d46859753a 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -147,8 +147,8 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, if (!(iint->flags & IMA_COLLECTED)) { u64 i_version = file->f_dentry->d_inode->i_version; - memset(iint->digest, 0, IMA_DIGEST_SIZE); - result = ima_calc_hash(file, iint->digest); + iint->ima_xattr.type = IMA_XATTR_DIGEST; + result = ima_calc_hash(file, iint->ima_xattr.digest); if (!result) { iint->version = i_version; iint->flags |= IMA_COLLECTED; @@ -196,7 +196,7 @@ void ima_store_measurement(struct integrity_iint_cache *iint, return; } memset(&entry->template, 0, sizeof(entry->template)); - memcpy(entry->template.digest, iint->digest, IMA_DIGEST_SIZE); + memcpy(entry->template.digest, iint->ima_xattr.digest, IMA_DIGEST_SIZE); strcpy(entry->template.file_name, (strlen(filename) > IMA_EVENT_NAME_LEN_MAX) ? file->f_dentry->d_name.name : filename); diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index becc7e09116d..f9979976aa5d 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -45,9 +45,9 @@ int ima_must_appraise(struct inode *inode, enum ima_hooks func, int mask) static void ima_fix_xattr(struct dentry *dentry, struct integrity_iint_cache *iint) { - iint->digest[0] = IMA_XATTR_DIGEST; - __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, - iint->digest, IMA_DIGEST_SIZE + 1, 0); + iint->ima_xattr.type = IMA_XATTR_DIGEST; + __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, (u8 *)&iint->ima_xattr, + sizeof iint->ima_xattr, 0); } /* @@ -63,7 +63,7 @@ int ima_appraise_measurement(struct integrity_iint_cache *iint, { struct dentry *dentry = file->f_dentry; struct inode *inode = dentry->d_inode; - u8 xattr_value[IMA_DIGEST_SIZE]; + struct evm_ima_xattr_data xattr_value; enum integrity_status status = INTEGRITY_UNKNOWN; const char *op = "appraise_data"; char *cause = "unknown"; @@ -77,8 +77,8 @@ int ima_appraise_measurement(struct integrity_iint_cache *iint, if (iint->flags & IMA_APPRAISED) return iint->ima_status; - rc = inode->i_op->getxattr(dentry, XATTR_NAME_IMA, xattr_value, - IMA_DIGEST_SIZE); + rc = inode->i_op->getxattr(dentry, XATTR_NAME_IMA, (u8 *)&xattr_value, + sizeof xattr_value); if (rc <= 0) { if (rc && rc != -ENODATA) goto out; @@ -89,7 +89,8 @@ int ima_appraise_measurement(struct integrity_iint_cache *iint, goto out; } - status = evm_verifyxattr(dentry, XATTR_NAME_IMA, xattr_value, rc, iint); + status = evm_verifyxattr(dentry, XATTR_NAME_IMA, (u8 *)&xattr_value, + rc, iint); if ((status != INTEGRITY_PASS) && (status != INTEGRITY_UNKNOWN)) { if ((status == INTEGRITY_NOLABEL) || (status == INTEGRITY_NOXATTRS)) @@ -99,14 +100,16 @@ int ima_appraise_measurement(struct integrity_iint_cache *iint, goto out; } - rc = memcmp(xattr_value, iint->digest, IMA_DIGEST_SIZE); + rc = memcmp(xattr_value.digest, iint->ima_xattr.digest, + IMA_DIGEST_SIZE); if (rc) { status = INTEGRITY_FAIL; cause = "invalid-hash"; print_hex_dump_bytes("security.ima: ", DUMP_PREFIX_NONE, - xattr_value, IMA_DIGEST_SIZE); + &xattr_value, sizeof xattr_value); print_hex_dump_bytes("collected: ", DUMP_PREFIX_NONE, - iint->digest, IMA_DIGEST_SIZE); + (u8 *)&iint->ima_xattr, + sizeof iint->ima_xattr); goto out; } status = INTEGRITY_PASS; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index dac6b68e945a..91ccef1c704b 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -39,7 +39,7 @@ struct integrity_iint_cache { struct inode *inode; /* back pointer to inode in question */ u64 version; /* track inode changes */ unsigned char flags; - u8 digest[SHA1_DIGEST_SIZE]; + struct evm_ima_xattr_data ima_xattr; enum integrity_status ima_status; enum integrity_status evm_status; }; -- cgit v1.2.3 From 8606404fa555c2ee691376fcc640ab89fe752035 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Wed, 31 Aug 2011 14:07:06 +0300 Subject: ima: digital signature verification support This patch adds support for digital signature based integrity appraisal. With this patch, 'security.ima' contains either the file data hash or a digital signature of the file data hash. The file data hash provides the security attribute of file integrity. In addition to file integrity, a digital signature provides the security attribute of authenticity. Unlike EVM, when the file metadata changes, the digital signature is replaced with an HMAC, modification of the file data does not cause the 'security.ima' digital signature to be replaced with a hash. As a result, after any modification, subsequent file integrity appraisals would fail. Although digitally signed files can be modified, but by not updating 'security.ima' to reflect these modifications, in essence digitally signed files could be considered 'immutable'. IMA uses a different keyring than EVM. While the EVM keyring should not be updated after initialization and locked, the IMA keyring should allow updating or adding new keys when upgrading or installing packages. Changelog v4: - Change IMA_DIGSIG to hex equivalent Changelog v3: - Permit files without any 'security.ima' xattr to be labeled properly. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 70 +++++++++++++++++++++++++---------- security/integrity/integrity.h | 1 + 2 files changed, 52 insertions(+), 19 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index f9979976aa5d..4cdf36ad884a 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -63,7 +63,7 @@ int ima_appraise_measurement(struct integrity_iint_cache *iint, { struct dentry *dentry = file->f_dentry; struct inode *inode = dentry->d_inode; - struct evm_ima_xattr_data xattr_value; + struct evm_ima_xattr_data *xattr_value = NULL; enum integrity_status status = INTEGRITY_UNKNOWN; const char *op = "appraise_data"; char *cause = "unknown"; @@ -77,8 +77,8 @@ int ima_appraise_measurement(struct integrity_iint_cache *iint, if (iint->flags & IMA_APPRAISED) return iint->ima_status; - rc = inode->i_op->getxattr(dentry, XATTR_NAME_IMA, (u8 *)&xattr_value, - sizeof xattr_value); + rc = vfs_getxattr_alloc(dentry, XATTR_NAME_IMA, (char **)&xattr_value, + 0, GFP_NOFS); if (rc <= 0) { if (rc && rc != -ENODATA) goto out; @@ -89,8 +89,7 @@ int ima_appraise_measurement(struct integrity_iint_cache *iint, goto out; } - status = evm_verifyxattr(dentry, XATTR_NAME_IMA, (u8 *)&xattr_value, - rc, iint); + status = evm_verifyxattr(dentry, XATTR_NAME_IMA, xattr_value, rc, iint); if ((status != INTEGRITY_PASS) && (status != INTEGRITY_UNKNOWN)) { if ((status == INTEGRITY_NOLABEL) || (status == INTEGRITY_NOXATTRS)) @@ -100,30 +99,58 @@ int ima_appraise_measurement(struct integrity_iint_cache *iint, goto out; } - rc = memcmp(xattr_value.digest, iint->ima_xattr.digest, - IMA_DIGEST_SIZE); - if (rc) { - status = INTEGRITY_FAIL; - cause = "invalid-hash"; - print_hex_dump_bytes("security.ima: ", DUMP_PREFIX_NONE, - &xattr_value, sizeof xattr_value); - print_hex_dump_bytes("collected: ", DUMP_PREFIX_NONE, - (u8 *)&iint->ima_xattr, - sizeof iint->ima_xattr); - goto out; + switch (xattr_value->type) { + case IMA_XATTR_DIGEST: + rc = memcmp(xattr_value->digest, iint->ima_xattr.digest, + IMA_DIGEST_SIZE); + if (rc) { + cause = "invalid-hash"; + status = INTEGRITY_FAIL; + print_hex_dump_bytes("security.ima: ", DUMP_PREFIX_NONE, + xattr_value, sizeof(*xattr_value)); + print_hex_dump_bytes("collected: ", DUMP_PREFIX_NONE, + (u8 *)&iint->ima_xattr, + sizeof iint->ima_xattr); + break; + } + status = INTEGRITY_PASS; + break; + case EVM_IMA_XATTR_DIGSIG: + iint->flags |= IMA_DIGSIG; + rc = integrity_digsig_verify(INTEGRITY_KEYRING_IMA, + xattr_value->digest, rc - 1, + iint->ima_xattr.digest, + IMA_DIGEST_SIZE); + if (rc == -EOPNOTSUPP) { + status = INTEGRITY_UNKNOWN; + } else if (rc) { + cause = "invalid-signature"; + status = INTEGRITY_FAIL; + } else { + status = INTEGRITY_PASS; + } + break; + default: + status = INTEGRITY_UNKNOWN; + cause = "unknown-ima-data"; + break; } - status = INTEGRITY_PASS; - iint->flags |= IMA_APPRAISED; + out: if (status != INTEGRITY_PASS) { - if (ima_appraise & IMA_APPRAISE_FIX) { + if ((ima_appraise & IMA_APPRAISE_FIX) && + (!xattr_value || + xattr_value->type != EVM_IMA_XATTR_DIGSIG)) { ima_fix_xattr(dentry, iint); status = INTEGRITY_PASS; } integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, filename, op, cause, rc, 0); + } else { + iint->flags |= IMA_APPRAISED; } iint->ima_status = status; + kfree(xattr_value); return status; } @@ -135,9 +162,14 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) struct dentry *dentry = file->f_dentry; int rc = 0; + /* do not collect and update hash for digital signatures */ + if (iint->flags & IMA_DIGSIG) + return; + rc = ima_collect_measurement(iint, file); if (rc < 0) return; + ima_fix_xattr(dentry, iint); } diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 91ccef1c704b..4eec1b14193e 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -21,6 +21,7 @@ #define IMA_APPRAISE 0x04 #define IMA_APPRAISED 0x08 #define IMA_COLLECTED 0x10 +#define IMA_DIGSIG 0x20 enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, -- cgit v1.2.3 From 9785e10aedfa0fad5c1aac709dce5ada1b123783 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 8 Sep 2012 02:53:53 +0000 Subject: netlink: kill netlink_set_nonroot Replace netlink_set_nonroot by one new field `flags' in struct netlink_kernel_cfg that is passed to netlink_kernel_create. This patch also renames NL_NONROOT_* to NL_CFG_F_NONROOT_* since now the flags field in nl_table is generic (so we can add more flags if needed in the future). Also adjust all callers in the net-next tree to use these flags instead of netlink_set_nonroot. Signed-off-by: Pablo Neira Ayuso Signed-off-by: David S. Miller --- security/selinux/netlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c index 8a77725423e0..0d2cd11f3c22 100644 --- a/security/selinux/netlink.c +++ b/security/selinux/netlink.c @@ -113,13 +113,13 @@ static int __init selnl_init(void) { struct netlink_kernel_cfg cfg = { .groups = SELNLGRP_MAX, + .flags = NL_CFG_F_NONROOT_RECV, }; selnl = netlink_kernel_create(&init_net, NETLINK_SELINUX, THIS_MODULE, &cfg); if (selnl == NULL) panic("SELinux: Cannot create netlink socket."); - netlink_set_nonroot(NETLINK_SELINUX, NL_NONROOT_RECV); return 0; } -- cgit v1.2.3 From 9f00d9776bc5beb92e8bfc884a7e96ddc5589e2e Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Sat, 8 Sep 2012 02:53:54 +0000 Subject: netlink: hide struct module parameter in netlink_kernel_create This patch defines netlink_kernel_create as a wrapper function of __netlink_kernel_create to hide the struct module *me parameter (which seems to be THIS_MODULE in all existing netlink subsystems). Suggested by David S. Miller. Signed-off-by: Pablo Neira Ayuso Signed-off-by: David S. Miller --- security/selinux/netlink.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'security') diff --git a/security/selinux/netlink.c b/security/selinux/netlink.c index 0d2cd11f3c22..14d810ead420 100644 --- a/security/selinux/netlink.c +++ b/security/selinux/netlink.c @@ -116,8 +116,7 @@ static int __init selnl_init(void) .flags = NL_CFG_F_NONROOT_RECV, }; - selnl = netlink_kernel_create(&init_net, NETLINK_SELINUX, - THIS_MODULE, &cfg); + selnl = netlink_kernel_create(&init_net, NETLINK_SELINUX, &cfg); if (selnl == NULL) panic("SELinux: Cannot create netlink socket."); return 0; -- cgit v1.2.3 From d9d300cdb6f233c4c591348919c758062198a4f4 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Wed, 27 Jun 2012 11:26:14 +0300 Subject: ima: rename ima_must_appraise_or_measure When AUDIT action support is added to the IMA, ima_must_appraise_or_measure() does not reflect the real meaning anymore. Rename it to ima_get_action(). Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 2 +- security/integrity/ima/ima_api.c | 4 ++-- security/integrity/ima/ima_main.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 069a4aa63e95..48aa0d46d3e7 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -108,7 +108,7 @@ static inline unsigned long ima_hash_key(u8 *digest) } /* LIM API function definitions */ -int ima_must_appraise_or_measure(struct inode *inode, int mask, int function); +int ima_get_action(struct inode *inode, int mask, int function); int ima_must_measure(struct inode *inode, int mask, int function); int ima_collect_measurement(struct integrity_iint_cache *iint, struct file *file); diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 33d46859753a..f0d60e754b3e 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -97,7 +97,7 @@ err_out: } /** - * ima_must_appraise_or_measure - appraise & measure decision based on policy. + * ima_get_action - appraise & measure decision based on policy. * @inode: pointer to inode to measure * @mask: contains the permission mask (MAY_READ, MAY_WRITE, MAY_EXECUTE) * @function: calling function (FILE_CHECK, BPRM_CHECK, FILE_MMAP) @@ -112,7 +112,7 @@ err_out: * Returns IMA_MEASURE, IMA_APPRAISE mask. * */ -int ima_must_appraise_or_measure(struct inode *inode, int mask, int function) +int ima_get_action(struct inode *inode, int mask, int function) { int flags = IMA_MEASURE | IMA_APPRAISE; diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index df6521296051..60b047e96f4e 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -158,7 +158,7 @@ static int process_measurement(struct file *file, const unsigned char *filename, /* Determine if in appraise/measurement policy, * returns IMA_MEASURE, IMA_APPRAISE bitmask. */ - action = ima_must_appraise_or_measure(inode, mask, function); + action = ima_get_action(inode, mask, function); if (!action) return 0; -- cgit v1.2.3 From b3f68f16dbcde6fcdf0fd27695391ff7e9d41233 Mon Sep 17 00:00:00 2001 From: Oleg Nesterov Date: Sun, 26 Aug 2012 21:12:14 +0200 Subject: task_work: Revert "hold task_lock around checks in keyctl" This reverts commit d35abdb28824cf74f0a106a0f9c6f3ff700a35bf. task_lock() was added to ensure exit_mm() and thus exit_task_work() is not possible before task_work_add(). This is wrong, task_lock() must not be nested with write_lock(tasklist). And this is no longer needed, task_work_add() now fails if it is called after exit_task_work(). Reported-by: Dave Jones Signed-off-by: Oleg Nesterov Signed-off-by: Peter Zijlstra Cc: Al Viro Cc: Linus Torvalds Cc: Andrew Morton Link: http://lkml.kernel.org/r/20120826191214.GA4231@redhat.com Signed-off-by: Ingo Molnar --- security/keys/keyctl.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'security') diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 3364fbf46807..6cfc6478863e 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -1486,7 +1486,6 @@ long keyctl_session_to_parent(void) oldwork = NULL; parent = me->real_parent; - task_lock(parent); /* the parent mustn't be init and mustn't be a kernel thread */ if (parent->pid <= 1 || !parent->mm) goto unlock; @@ -1530,7 +1529,6 @@ long keyctl_session_to_parent(void) if (!ret) newwork = NULL; unlock: - task_unlock(parent); write_unlock_irq(&tasklist_lock); rcu_read_unlock(); if (oldwork) -- cgit v1.2.3 From 45e2472e67bf66f794d507b52e82af92e0614e49 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Wed, 12 Sep 2012 20:51:32 +0300 Subject: ima: generic IMA action flag handling Make the IMA action flag handling generic in order to support additional new actions, without requiring changes to the base implementation. New actions, like audit logging, will only need to modify the define statements. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/ima/ima_appraise.c | 2 +- security/integrity/ima/ima_main.c | 4 ++-- security/integrity/ima/ima_policy.c | 21 +++++++++++---------- security/integrity/integrity.h | 18 ++++++++++++------ 4 files changed, 26 insertions(+), 19 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 4cdf36ad884a..0aa43bde441c 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -232,7 +232,7 @@ static void ima_reset_appraise_flags(struct inode *inode) if (!iint) return; - iint->flags &= ~(IMA_COLLECTED | IMA_APPRAISED | IMA_MEASURED); + iint->flags &= ~IMA_DONE_MASK; return; } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 60b047e96f4e..5da08b75d367 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -117,7 +117,7 @@ static void ima_check_last_writer(struct integrity_iint_cache *iint, mutex_lock(&inode->i_mutex); if (atomic_read(&inode->i_writecount) == 1 && iint->version != inode->i_version) { - iint->flags &= ~(IMA_COLLECTED | IMA_APPRAISED | IMA_MEASURED); + iint->flags &= ~IMA_DONE_MASK; if (iint->flags & IMA_APPRAISE) ima_update_xattr(iint, file); } @@ -173,7 +173,7 @@ static int process_measurement(struct file *file, const unsigned char *filename, /* Determine if already appraised/measured based on bitmask * (IMA_MEASURE, IMA_MEASURED, IMA_APPRAISE, IMA_APPRAISED) */ iint->flags |= action; - action &= ~((iint->flags & (IMA_MEASURED | IMA_APPRAISED)) >> 1); + action &= ~((iint->flags & IMA_DONE_MASK) >> 1); /* Nothing to do, just return existing appraised status */ if (!action) { diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 0d6d60b4ba6f..f46f685a1711 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -26,13 +26,11 @@ #define IMA_UID 0x0008 #define IMA_FOWNER 0x0010 -#define UNKNOWN 0 -#define MEASURE 1 /* same as IMA_MEASURE */ -#define DONT_MEASURE 2 -#define MEASURE_MASK 3 -#define APPRAISE 4 /* same as IMA_APPRAISE */ -#define DONT_APPRAISE 8 -#define APPRAISE_MASK 12 +#define UNKNOWN 0 +#define MEASURE 0x0001 /* same as IMA_MEASURE */ +#define DONT_MEASURE 0x0002 +#define APPRAISE 0x0004 /* same as IMA_APPRAISE */ +#define DONT_APPRAISE 0x0008 #define MAX_LSM_RULES 6 enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, @@ -209,9 +207,12 @@ int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask, if (!ima_match_rules(entry, inode, func, mask)) continue; - action |= (entry->action & (IMA_APPRAISE | IMA_MEASURE)); - actmask &= (entry->action & APPRAISE_MASK) ? - ~APPRAISE_MASK : ~MEASURE_MASK; + action |= entry->action & IMA_DO_MASK; + if (entry->action & IMA_DO_MASK) + actmask &= ~(entry->action | entry->action << 1); + else + actmask &= ~(entry->action | entry->action >> 1); + if (!actmask) break; } diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 4eec1b14193e..564ba7db5f6a 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -15,13 +15,19 @@ #include #include +/* iint action cache flags */ +#define IMA_MEASURE 0x0001 +#define IMA_MEASURED 0x0002 +#define IMA_APPRAISE 0x0004 +#define IMA_APPRAISED 0x0008 +/*#define IMA_COLLECT 0x0010 do not use this flag */ +#define IMA_COLLECTED 0x0020 + /* iint cache flags */ -#define IMA_MEASURE 0x01 -#define IMA_MEASURED 0x02 -#define IMA_APPRAISE 0x04 -#define IMA_APPRAISED 0x08 -#define IMA_COLLECTED 0x10 -#define IMA_DIGSIG 0x20 +#define IMA_DIGSIG 0x0100 + +#define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE) +#define IMA_DONE_MASK (IMA_MEASURED | IMA_APPRAISED | IMA_COLLECTED) enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, -- cgit v1.2.3 From e7c568e0fd0cf6d9c8ab8ea537ba8f3a3ae7c3d8 Mon Sep 17 00:00:00 2001 From: Peter Moody Date: Thu, 14 Jun 2012 10:04:36 -0700 Subject: ima: audit log hashes This adds an 'audit' policy action which audit logs file measurements. Changelog v6: - use new action flag handling (Dmitry Kasatkin). - removed whitespace (Mimi) Changelog v5: - use audit_log_untrustedstring. Changelog v4: - cleanup digest -> hash conversion. - use filename rather than d_path in ima_audit_measurement. Changelog v3: - Use newly exported audit_log_task_info for logging pid/ppid/uid/etc. - Update the ima_policy ABI documentation. Changelog v2: - Use 'audit' action rather than 'measure_and_audit' to permit auditing in the absence of measuring.. Changelog v1: - Initial posting. Signed-off-by: Peter Moody Signed-off-by: Mimi Zohar --- security/integrity/ima/ima.h | 2 ++ security/integrity/ima/ima_api.c | 32 +++++++++++++++++++++++++++++++- security/integrity/ima/ima_main.c | 9 ++++++--- security/integrity/ima/ima_policy.c | 11 +++++++++++ security/integrity/integrity.h | 7 +++++-- 5 files changed, 55 insertions(+), 6 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 48aa0d46d3e7..8180adde10b7 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -114,6 +114,8 @@ int ima_collect_measurement(struct integrity_iint_cache *iint, struct file *file); void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename); +void ima_audit_measurement(struct integrity_iint_cache *iint, + const unsigned char *filename); int ima_store_template(struct ima_template_entry *entry, int violation, struct inode *inode); void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show); diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index f0d60e754b3e..b356884fb3ef 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -114,7 +114,7 @@ err_out: */ int ima_get_action(struct inode *inode, int mask, int function) { - int flags = IMA_MEASURE | IMA_APPRAISE; + int flags = IMA_MEASURE | IMA_AUDIT | IMA_APPRAISE; if (!ima_appraise) flags &= ~IMA_APPRAISE; @@ -207,3 +207,33 @@ void ima_store_measurement(struct integrity_iint_cache *iint, if (result < 0) kfree(entry); } + +void ima_audit_measurement(struct integrity_iint_cache *iint, + const unsigned char *filename) +{ + struct audit_buffer *ab; + char hash[(IMA_DIGEST_SIZE * 2) + 1]; + int i; + + if (iint->flags & IMA_AUDITED) + return; + + for (i = 0; i < IMA_DIGEST_SIZE; i++) + hex_byte_pack(hash + (i * 2), iint->ima_xattr.digest[i]); + hash[i * 2] = '\0'; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_RULE); + if (!ab) + return; + + audit_log_format(ab, "file="); + audit_log_untrustedstring(ab, filename); + audit_log_format(ab, " hash="); + audit_log_untrustedstring(ab, hash); + + audit_log_task_info(ab, current); + audit_log_end(ab); + + iint->flags |= IMA_AUDITED; +} diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 5da08b75d367..73c9a268253e 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -156,8 +156,8 @@ static int process_measurement(struct file *file, const unsigned char *filename, if (!ima_initialized || !S_ISREG(inode->i_mode)) return 0; - /* Determine if in appraise/measurement policy, - * returns IMA_MEASURE, IMA_APPRAISE bitmask. */ + /* Determine if in appraise/audit/measurement policy, + * returns IMA_MEASURE, IMA_APPRAISE, IMA_AUDIT bitmask. */ action = ima_get_action(inode, mask, function); if (!action) return 0; @@ -171,7 +171,8 @@ static int process_measurement(struct file *file, const unsigned char *filename, goto out; /* Determine if already appraised/measured based on bitmask - * (IMA_MEASURE, IMA_MEASURED, IMA_APPRAISE, IMA_APPRAISED) */ + * (IMA_MEASURE, IMA_MEASURED, IMA_APPRAISE, IMA_APPRAISED, + * IMA_AUDIT, IMA_AUDITED) */ iint->flags |= action; action &= ~((iint->flags & IMA_DONE_MASK) >> 1); @@ -202,6 +203,8 @@ static int process_measurement(struct file *file, const unsigned char *filename, if (action & IMA_APPRAISE) rc = ima_appraise_measurement(iint, file, !pathname ? filename : pathname); + if (action & IMA_AUDIT) + ima_audit_measurement(iint, !pathname ? filename : pathname); kfree(pathbuf); out: mutex_unlock(&inode->i_mutex); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index f46f685a1711..cda903131dbf 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -31,6 +31,7 @@ #define DONT_MEASURE 0x0002 #define APPRAISE 0x0004 /* same as IMA_APPRAISE */ #define DONT_APPRAISE 0x0008 +#define AUDIT 0x0040 #define MAX_LSM_RULES 6 enum lsm_rule_types { LSM_OBJ_USER, LSM_OBJ_ROLE, LSM_OBJ_TYPE, @@ -277,6 +278,7 @@ enum { Opt_err = -1, Opt_measure = 1, Opt_dont_measure, Opt_appraise, Opt_dont_appraise, + Opt_audit, Opt_obj_user, Opt_obj_role, Opt_obj_type, Opt_subj_user, Opt_subj_role, Opt_subj_type, Opt_func, Opt_mask, Opt_fsmagic, Opt_uid, Opt_fowner @@ -287,6 +289,7 @@ static match_table_t policy_tokens = { {Opt_dont_measure, "dont_measure"}, {Opt_appraise, "appraise"}, {Opt_dont_appraise, "dont_appraise"}, + {Opt_audit, "audit"}, {Opt_obj_user, "obj_user=%s"}, {Opt_obj_role, "obj_role=%s"}, {Opt_obj_type, "obj_type=%s"}, @@ -379,6 +382,14 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->action = DONT_APPRAISE; break; + case Opt_audit: + ima_log_string(ab, "action", "audit"); + + if (entry->action != UNKNOWN) + result = -EINVAL; + + entry->action = AUDIT; + break; case Opt_func: ima_log_string(ab, "func", args[0].from); diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 564ba7db5f6a..403ba319a06a 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -22,12 +22,15 @@ #define IMA_APPRAISED 0x0008 /*#define IMA_COLLECT 0x0010 do not use this flag */ #define IMA_COLLECTED 0x0020 +#define IMA_AUDIT 0x0040 +#define IMA_AUDITED 0x0080 /* iint cache flags */ #define IMA_DIGSIG 0x0100 -#define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE) -#define IMA_DONE_MASK (IMA_MEASURED | IMA_APPRAISED | IMA_COLLECTED) +#define IMA_DO_MASK (IMA_MEASURE | IMA_APPRAISE | IMA_AUDIT) +#define IMA_DONE_MASK (IMA_MEASURED | IMA_APPRAISED | IMA_AUDITED \ + | IMA_COLLECTED) enum evm_ima_xattr_type { IMA_XATTR_DIGEST = 0x01, -- cgit v1.2.3 From 9a56c2db49e7349c7963f0ce66c1ef578d44ebd3 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Wed, 8 Feb 2012 07:53:04 -0800 Subject: userns: Convert security/keys to the new userns infrastructure - Replace key_user ->user_ns equality checks with kuid_has_mapping checks. - Use from_kuid to generate key descriptions - Use kuid_t and kgid_t and the associated helpers instead of uid_t and gid_t - Avoid potential problems with file descriptor passing by displaying keys in the user namespace of the opener of key status proc files. Cc: linux-security-module@vger.kernel.org Cc: keyrings@linux-nfs.org Cc: David Howells Signed-off-by: Eric W. Biederman --- security/keys/internal.h | 6 ++---- security/keys/key.c | 23 +++++++------------- security/keys/keyctl.c | 50 ++++++++++++++++++++++++++------------------ security/keys/keyring.c | 4 ++-- security/keys/permission.c | 14 ++++--------- security/keys/proc.c | 44 +++++++++++++++++++------------------- security/keys/process_keys.c | 15 ++++++------- security/keys/request_key.c | 6 +++--- 8 files changed, 79 insertions(+), 83 deletions(-) (limited to 'security') diff --git a/security/keys/internal.h b/security/keys/internal.h index 22ff05269e3d..8bbefc3b55d4 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -52,8 +52,7 @@ struct key_user { atomic_t usage; /* for accessing qnkeys & qnbytes */ atomic_t nkeys; /* number of keys */ atomic_t nikeys; /* number of instantiated keys */ - uid_t uid; - struct user_namespace *user_ns; + kuid_t uid; int qnkeys; /* number of keys allocated to this user */ int qnbytes; /* number of bytes allocated to this user */ }; @@ -62,8 +61,7 @@ extern struct rb_root key_user_tree; extern spinlock_t key_user_lock; extern struct key_user root_key_user; -extern struct key_user *key_user_lookup(uid_t uid, - struct user_namespace *user_ns); +extern struct key_user *key_user_lookup(kuid_t uid); extern void key_user_put(struct key_user *user); /* diff --git a/security/keys/key.c b/security/keys/key.c index 50d96d4e06f2..4289c5ba2710 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -18,7 +18,6 @@ #include #include #include -#include #include "internal.h" struct kmem_cache *key_jar; @@ -52,7 +51,7 @@ void __key_check(const struct key *key) * Get the key quota record for a user, allocating a new record if one doesn't * already exist. */ -struct key_user *key_user_lookup(uid_t uid, struct user_namespace *user_ns) +struct key_user *key_user_lookup(kuid_t uid) { struct key_user *candidate = NULL, *user; struct rb_node *parent = NULL; @@ -67,13 +66,9 @@ try_again: parent = *p; user = rb_entry(parent, struct key_user, node); - if (uid < user->uid) + if (uid_lt(uid, user->uid)) p = &(*p)->rb_left; - else if (uid > user->uid) - p = &(*p)->rb_right; - else if (user_ns < user->user_ns) - p = &(*p)->rb_left; - else if (user_ns > user->user_ns) + else if (uid_gt(uid, user->uid)) p = &(*p)->rb_right; else goto found; @@ -102,7 +97,6 @@ try_again: atomic_set(&candidate->nkeys, 0); atomic_set(&candidate->nikeys, 0); candidate->uid = uid; - candidate->user_ns = get_user_ns(user_ns); candidate->qnkeys = 0; candidate->qnbytes = 0; spin_lock_init(&candidate->lock); @@ -131,7 +125,6 @@ void key_user_put(struct key_user *user) if (atomic_dec_and_lock(&user->usage, &key_user_lock)) { rb_erase(&user->node, &key_user_tree); spin_unlock(&key_user_lock); - put_user_ns(user->user_ns); kfree(user); } @@ -229,7 +222,7 @@ serial_exists: * key_alloc() calls don't race with module unloading. */ struct key *key_alloc(struct key_type *type, const char *desc, - uid_t uid, gid_t gid, const struct cred *cred, + kuid_t uid, kgid_t gid, const struct cred *cred, key_perm_t perm, unsigned long flags) { struct key_user *user = NULL; @@ -253,16 +246,16 @@ struct key *key_alloc(struct key_type *type, const char *desc, quotalen = desclen + type->def_datalen; /* get hold of the key tracking for this user */ - user = key_user_lookup(uid, cred->user_ns); + user = key_user_lookup(uid); if (!user) goto no_memory_1; /* check that the user's quota permits allocation of another key and * its description */ if (!(flags & KEY_ALLOC_NOT_IN_QUOTA)) { - unsigned maxkeys = (uid == 0) ? + unsigned maxkeys = uid_eq(uid, GLOBAL_ROOT_UID) ? key_quota_root_maxkeys : key_quota_maxkeys; - unsigned maxbytes = (uid == 0) ? + unsigned maxbytes = uid_eq(uid, GLOBAL_ROOT_UID) ? key_quota_root_maxbytes : key_quota_maxbytes; spin_lock(&user->lock); @@ -380,7 +373,7 @@ int key_payload_reserve(struct key *key, size_t datalen) /* contemplate the quota adjustment */ if (delta != 0 && test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { - unsigned maxbytes = (key->user->uid == 0) ? + unsigned maxbytes = uid_eq(key->user->uid, GLOBAL_ROOT_UID) ? key_quota_root_maxbytes : key_quota_maxbytes; spin_lock(&key->user->lock); diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 3364fbf46807..1ecc0f79906e 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -569,8 +569,8 @@ okay: ret = snprintf(tmpbuf, PAGE_SIZE - 1, "%s;%d;%d;%08x;%s", key->type->name, - key->uid, - key->gid, + from_kuid_munged(current_user_ns(), key->uid), + from_kgid_munged(current_user_ns(), key->gid), key->perm, key->description ?: ""); @@ -766,15 +766,25 @@ error: * * If successful, 0 will be returned. */ -long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) +long keyctl_chown_key(key_serial_t id, uid_t user, gid_t group) { struct key_user *newowner, *zapowner = NULL; struct key *key; key_ref_t key_ref; long ret; + kuid_t uid; + kgid_t gid; + + uid = make_kuid(current_user_ns(), user); + gid = make_kgid(current_user_ns(), group); + ret = -EINVAL; + if ((user != (uid_t) -1) && !uid_valid(uid)) + goto error; + if ((group != (gid_t) -1) && !gid_valid(gid)) + goto error; ret = 0; - if (uid == (uid_t) -1 && gid == (gid_t) -1) + if (user == (uid_t) -1 && group == (gid_t) -1) goto error; key_ref = lookup_user_key(id, KEY_LOOKUP_CREATE | KEY_LOOKUP_PARTIAL, @@ -792,27 +802,27 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) if (!capable(CAP_SYS_ADMIN)) { /* only the sysadmin can chown a key to some other UID */ - if (uid != (uid_t) -1 && key->uid != uid) + if (user != (uid_t) -1 && !uid_eq(key->uid, uid)) goto error_put; /* only the sysadmin can set the key's GID to a group other * than one of those that the current process subscribes to */ - if (gid != (gid_t) -1 && gid != key->gid && !in_group_p(gid)) + if (group != (gid_t) -1 && !gid_eq(gid, key->gid) && !in_group_p(gid)) goto error_put; } /* change the UID */ - if (uid != (uid_t) -1 && uid != key->uid) { + if (user != (uid_t) -1 && !uid_eq(uid, key->uid)) { ret = -ENOMEM; - newowner = key_user_lookup(uid, current_user_ns()); + newowner = key_user_lookup(uid); if (!newowner) goto error_put; /* transfer the quota burden to the new user */ if (test_bit(KEY_FLAG_IN_QUOTA, &key->flags)) { - unsigned maxkeys = (uid == 0) ? + unsigned maxkeys = uid_eq(uid, GLOBAL_ROOT_UID) ? key_quota_root_maxkeys : key_quota_maxkeys; - unsigned maxbytes = (uid == 0) ? + unsigned maxbytes = uid_eq(uid, GLOBAL_ROOT_UID) ? key_quota_root_maxbytes : key_quota_maxbytes; spin_lock(&newowner->lock); @@ -846,7 +856,7 @@ long keyctl_chown_key(key_serial_t id, uid_t uid, gid_t gid) } /* change the GID */ - if (gid != (gid_t) -1) + if (group != (gid_t) -1) key->gid = gid; ret = 0; @@ -897,7 +907,7 @@ long keyctl_setperm_key(key_serial_t id, key_perm_t perm) down_write(&key->sem); /* if we're not the sysadmin, we can only change a key that we own */ - if (capable(CAP_SYS_ADMIN) || key->uid == current_fsuid()) { + if (capable(CAP_SYS_ADMIN) || uid_eq(key->uid, current_fsuid())) { key->perm = perm; ret = 0; } @@ -1507,18 +1517,18 @@ long keyctl_session_to_parent(void) /* the parent must have the same effective ownership and mustn't be * SUID/SGID */ - if (pcred->uid != mycred->euid || - pcred->euid != mycred->euid || - pcred->suid != mycred->euid || - pcred->gid != mycred->egid || - pcred->egid != mycred->egid || - pcred->sgid != mycred->egid) + if (!uid_eq(pcred->uid, mycred->euid) || + !uid_eq(pcred->euid, mycred->euid) || + !uid_eq(pcred->suid, mycred->euid) || + !gid_eq(pcred->gid, mycred->egid) || + !gid_eq(pcred->egid, mycred->egid) || + !gid_eq(pcred->sgid, mycred->egid)) goto unlock; /* the keyrings must have the same UID */ if ((pcred->tgcred->session_keyring && - pcred->tgcred->session_keyring->uid != mycred->euid) || - mycred->tgcred->session_keyring->uid != mycred->euid) + !uid_eq(pcred->tgcred->session_keyring->uid, mycred->euid)) || + !uid_eq(mycred->tgcred->session_keyring->uid, mycred->euid)) goto unlock; /* cancel an already pending keyring replacement */ diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 81e7852d281d..a5f5c4b6edc5 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -256,7 +256,7 @@ error: /* * Allocate a keyring and link into the destination keyring. */ -struct key *keyring_alloc(const char *description, uid_t uid, gid_t gid, +struct key *keyring_alloc(const char *description, kuid_t uid, kgid_t gid, const struct cred *cred, unsigned long flags, struct key *dest) { @@ -612,7 +612,7 @@ struct key *find_keyring_by_name(const char *name, bool skip_perm_check) &keyring_name_hash[bucket], type_data.link ) { - if (keyring->user->user_ns != current_user_ns()) + if (!kuid_has_mapping(current_user_ns(), keyring->user->uid)) continue; if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) diff --git a/security/keys/permission.c b/security/keys/permission.c index 0b4d019e027d..efcc0c855a0d 100644 --- a/security/keys/permission.c +++ b/security/keys/permission.c @@ -36,33 +36,27 @@ int key_task_permission(const key_ref_t key_ref, const struct cred *cred, key = key_ref_to_ptr(key_ref); - if (key->user->user_ns != cred->user_ns) - goto use_other_perms; - /* use the second 8-bits of permissions for keys the caller owns */ - if (key->uid == cred->fsuid) { + if (uid_eq(key->uid, cred->fsuid)) { kperm = key->perm >> 16; goto use_these_perms; } /* use the third 8-bits of permissions for keys the caller has a group * membership in common with */ - if (key->gid != -1 && key->perm & KEY_GRP_ALL) { - if (key->gid == cred->fsgid) { + if (gid_valid(key->gid) && key->perm & KEY_GRP_ALL) { + if (gid_eq(key->gid, cred->fsgid)) { kperm = key->perm >> 8; goto use_these_perms; } - ret = groups_search(cred->group_info, - make_kgid(current_user_ns(), key->gid)); + ret = groups_search(cred->group_info, key->gid); if (ret) { kperm = key->perm >> 8; goto use_these_perms; } } -use_other_perms: - /* otherwise use the least-significant 8-bits */ kperm = key->perm; diff --git a/security/keys/proc.c b/security/keys/proc.c index 30d1ddfd9cef..217b6855e815 100644 --- a/security/keys/proc.c +++ b/security/keys/proc.c @@ -88,14 +88,14 @@ __initcall(key_proc_init); */ #ifdef CONFIG_KEYS_DEBUG_PROC_KEYS -static struct rb_node *key_serial_next(struct rb_node *n) +static struct rb_node *key_serial_next(struct seq_file *p, struct rb_node *n) { - struct user_namespace *user_ns = current_user_ns(); + struct user_namespace *user_ns = seq_user_ns(p); n = rb_next(n); while (n) { struct key *key = rb_entry(n, struct key, serial_node); - if (key->user->user_ns == user_ns) + if (kuid_has_mapping(user_ns, key->user->uid)) break; n = rb_next(n); } @@ -107,9 +107,9 @@ static int proc_keys_open(struct inode *inode, struct file *file) return seq_open(file, &proc_keys_ops); } -static struct key *find_ge_key(key_serial_t id) +static struct key *find_ge_key(struct seq_file *p, key_serial_t id) { - struct user_namespace *user_ns = current_user_ns(); + struct user_namespace *user_ns = seq_user_ns(p); struct rb_node *n = key_serial_tree.rb_node; struct key *minkey = NULL; @@ -132,7 +132,7 @@ static struct key *find_ge_key(key_serial_t id) return NULL; for (;;) { - if (minkey->user->user_ns == user_ns) + if (kuid_has_mapping(user_ns, minkey->user->uid)) return minkey; n = rb_next(&minkey->serial_node); if (!n) @@ -151,7 +151,7 @@ static void *proc_keys_start(struct seq_file *p, loff_t *_pos) if (*_pos > INT_MAX) return NULL; - key = find_ge_key(pos); + key = find_ge_key(p, pos); if (!key) return NULL; *_pos = key->serial; @@ -168,7 +168,7 @@ static void *proc_keys_next(struct seq_file *p, void *v, loff_t *_pos) { struct rb_node *n; - n = key_serial_next(v); + n = key_serial_next(p, v); if (n) *_pos = key_node_serial(n); return n; @@ -254,8 +254,8 @@ static int proc_keys_show(struct seq_file *m, void *v) atomic_read(&key->usage), xbuf, key->perm, - key->uid, - key->gid, + from_kuid_munged(seq_user_ns(m), key->uid), + from_kgid_munged(seq_user_ns(m), key->gid), key->type->name); #undef showflag @@ -270,26 +270,26 @@ static int proc_keys_show(struct seq_file *m, void *v) #endif /* CONFIG_KEYS_DEBUG_PROC_KEYS */ -static struct rb_node *__key_user_next(struct rb_node *n) +static struct rb_node *__key_user_next(struct user_namespace *user_ns, struct rb_node *n) { while (n) { struct key_user *user = rb_entry(n, struct key_user, node); - if (user->user_ns == current_user_ns()) + if (kuid_has_mapping(user_ns, user->uid)) break; n = rb_next(n); } return n; } -static struct rb_node *key_user_next(struct rb_node *n) +static struct rb_node *key_user_next(struct user_namespace *user_ns, struct rb_node *n) { - return __key_user_next(rb_next(n)); + return __key_user_next(user_ns, rb_next(n)); } -static struct rb_node *key_user_first(struct rb_root *r) +static struct rb_node *key_user_first(struct user_namespace *user_ns, struct rb_root *r) { struct rb_node *n = rb_first(r); - return __key_user_next(n); + return __key_user_next(user_ns, n); } /* @@ -309,10 +309,10 @@ static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) spin_lock(&key_user_lock); - _p = key_user_first(&key_user_tree); + _p = key_user_first(seq_user_ns(p), &key_user_tree); while (pos > 0 && _p) { pos--; - _p = key_user_next(_p); + _p = key_user_next(seq_user_ns(p), _p); } return _p; @@ -321,7 +321,7 @@ static void *proc_key_users_start(struct seq_file *p, loff_t *_pos) static void *proc_key_users_next(struct seq_file *p, void *v, loff_t *_pos) { (*_pos)++; - return key_user_next((struct rb_node *)v); + return key_user_next(seq_user_ns(p), (struct rb_node *)v); } static void proc_key_users_stop(struct seq_file *p, void *v) @@ -334,13 +334,13 @@ static int proc_key_users_show(struct seq_file *m, void *v) { struct rb_node *_p = v; struct key_user *user = rb_entry(_p, struct key_user, node); - unsigned maxkeys = (user->uid == 0) ? + unsigned maxkeys = uid_eq(user->uid, GLOBAL_ROOT_UID) ? key_quota_root_maxkeys : key_quota_maxkeys; - unsigned maxbytes = (user->uid == 0) ? + unsigned maxbytes = uid_eq(user->uid, GLOBAL_ROOT_UID) ? key_quota_root_maxbytes : key_quota_maxbytes; seq_printf(m, "%5u: %5d %d/%d %d/%d %d/%d\n", - user->uid, + from_kuid_munged(seq_user_ns(m), user->uid), atomic_read(&user->usage), atomic_read(&user->nkeys), atomic_read(&user->nikeys), diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 54339cfd6734..a58f712605d8 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -34,8 +34,7 @@ struct key_user root_key_user = { .lock = __SPIN_LOCK_UNLOCKED(root_key_user.lock), .nkeys = ATOMIC_INIT(2), .nikeys = ATOMIC_INIT(2), - .uid = 0, - .user_ns = &init_user_ns, + .uid = GLOBAL_ROOT_UID, }; /* @@ -48,11 +47,13 @@ int install_user_keyrings(void) struct key *uid_keyring, *session_keyring; char buf[20]; int ret; + uid_t uid; cred = current_cred(); user = cred->user; + uid = from_kuid(cred->user_ns, user->uid); - kenter("%p{%u}", user, user->uid); + kenter("%p{%u}", user, uid); if (user->uid_keyring) { kleave(" = 0 [exist]"); @@ -67,11 +68,11 @@ int install_user_keyrings(void) * - there may be one in existence already as it may have been * pinned by a session, but the user_struct pointing to it * may have been destroyed by setuid */ - sprintf(buf, "_uid.%u", user->uid); + sprintf(buf, "_uid.%u", uid); uid_keyring = find_keyring_by_name(buf, true); if (IS_ERR(uid_keyring)) { - uid_keyring = keyring_alloc(buf, user->uid, (gid_t) -1, + uid_keyring = keyring_alloc(buf, user->uid, INVALID_GID, cred, KEY_ALLOC_IN_QUOTA, NULL); if (IS_ERR(uid_keyring)) { @@ -82,12 +83,12 @@ int install_user_keyrings(void) /* get a default session keyring (which might also exist * already) */ - sprintf(buf, "_uid_ses.%u", user->uid); + sprintf(buf, "_uid_ses.%u", uid); session_keyring = find_keyring_by_name(buf, true); if (IS_ERR(session_keyring)) { session_keyring = - keyring_alloc(buf, user->uid, (gid_t) -1, + keyring_alloc(buf, user->uid, INVALID_GID, cred, KEY_ALLOC_IN_QUOTA, NULL); if (IS_ERR(session_keyring)) { ret = PTR_ERR(session_keyring); diff --git a/security/keys/request_key.c b/security/keys/request_key.c index 000e75017520..66e21184b559 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -139,8 +139,8 @@ static int call_sbin_request_key(struct key_construction *cons, goto error_link; /* record the UID and GID */ - sprintf(uid_str, "%d", cred->fsuid); - sprintf(gid_str, "%d", cred->fsgid); + sprintf(uid_str, "%d", from_kuid(&init_user_ns, cred->fsuid)); + sprintf(gid_str, "%d", from_kgid(&init_user_ns, cred->fsgid)); /* we say which key is under construction */ sprintf(key_str, "%d", key->serial); @@ -442,7 +442,7 @@ static struct key *construct_key_and_link(struct key_type *type, kenter(""); - user = key_user_lookup(current_fsuid(), current_user_ns()); + user = key_user_lookup(current_fsuid()); if (!user) return ERR_PTR(-ENOMEM); -- cgit v1.2.3 From 8c7f6edbda01f1b1a2e60ad61f14fe38023e433b Mon Sep 17 00:00:00 2001 From: Tejun Heo Date: Thu, 13 Sep 2012 12:20:58 -0700 Subject: cgroup: mark subsystems with broken hierarchy support and whine if cgroups are nested for them Currently, cgroup hierarchy support is a mess. cpu related subsystems behave correctly - configuration, accounting and control on a parent properly cover its children. blkio and freezer completely ignore hierarchy and treat all cgroups as if they're directly under the root cgroup. Others show yet different behaviors. These differing interpretations of cgroup hierarchy make using cgroup confusing and it impossible to co-mount controllers into the same hierarchy and obtain sane behavior. Eventually, we want full hierarchy support from all subsystems and probably a unified hierarchy. Users using separate hierarchies expecting completely different behaviors depending on the mounted subsystem is deterimental to making any progress on this front. This patch adds cgroup_subsys.broken_hierarchy and sets it to %true for controllers which are lacking in hierarchy support. The goal of this patch is two-fold. * Move users away from using hierarchy on currently non-hierarchical subsystems, so that implementing proper hierarchy support on those doesn't surprise them. * Keep track of which controllers are broken how and nudge the subsystems to implement proper hierarchy support. For now, start with a single warning message. We can whine louder later on. v2: Fixed a typo spotted by Michal. Warning message updated. v3: Updated memcg part so that it doesn't generate warning in the cases where .use_hierarchy=false doesn't make the behavior different from root.use_hierarchy=true. Fixed a typo spotted by Glauber. v4: Check ->broken_hierarchy after cgroup creation is complete so that ->create() can affect the result per Michal. Dropped unnecessary memcg root handling per Michal. Signed-off-by: Tejun Heo Acked-by: Michal Hocko Acked-by: Li Zefan Acked-by: Serge E. Hallyn Cc: Glauber Costa Cc: Peter Zijlstra Cc: Paul Turner Cc: Johannes Weiner Cc: Thomas Graf Cc: Vivek Goyal Cc: Paul Mackerras Cc: Ingo Molnar Cc: Arnaldo Carvalho de Melo Cc: Neil Horman Cc: Aneesh Kumar K.V --- security/device_cgroup.c | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 442204cc22d9..4b877a92a7ea 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -457,6 +457,15 @@ struct cgroup_subsys devices_subsys = { .destroy = devcgroup_destroy, .subsys_id = devices_subsys_id, .base_cftypes = dev_cgroup_files, + + /* + * While devices cgroup has the rudimentary hierarchy support which + * checks the parent's restriction, it doesn't properly propagates + * config changes in ancestors to their descendents. A child + * should only be allowed to add more restrictions to the parent's + * configuration. Fix it and remove the following. + */ + .broken_hierarchy = true, }; int __devcgroup_inode_permission(struct inode *inode, int mask) -- cgit v1.2.3 From c00bedb368ae02a066aed8a888afc286c1df2e60 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Thu, 9 Aug 2012 17:46:38 -0700 Subject: Smack: remove task_wait() hook. On 12/20/2011 11:20 PM, Jarkko Sakkinen wrote: > Allow SIGCHLD to be passed to child process without > explicit policy. This will help to keep the access > control policy simple and easily maintainable with > complex applications that require use of multiple > security contexts. It will also help to keep them > as isolated as possible. > > Signed-off-by: Jarkko Sakkinen I have a slightly different version that applies to the current smack-next tree. Allow SIGCHLD to be passed to child process without explicit policy. This will help to keep the access control policy simple and easily maintainable with complex applications that require use of multiple security contexts. It will also help to keep them as isolated as possible. Signed-off-by: Casey Schaufler security/smack/smack_lsm.c | 37 ++++++++----------------------------- 1 files changed, 8 insertions(+), 29 deletions(-) --- security/smack/smack_lsm.c | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) (limited to 'security') diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 8221514cc997..ce9273a18165 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1691,40 +1691,19 @@ static int smack_task_kill(struct task_struct *p, struct siginfo *info, * smack_task_wait - Smack access check for waiting * @p: task to wait for * - * Returns 0 if current can wait for p, error code otherwise + * Returns 0 */ static int smack_task_wait(struct task_struct *p) { - struct smk_audit_info ad; - char *sp = smk_of_current(); - char *tsp = smk_of_forked(task_security(p)); - int rc; - - /* we don't log here, we can be overriden */ - rc = smk_access(tsp, sp, MAY_WRITE, NULL); - if (rc == 0) - goto out_log; - /* - * Allow the operation to succeed if either task - * has privilege to perform operations that might - * account for the smack labels having gotten to - * be different in the first place. - * - * This breaks the strict subject/object access - * control ideal, taking the object's privilege - * state into account in the decision as well as - * the smack value. + * Allow the operation to succeed. + * Zombies are bad. + * In userless environments (e.g. phones) programs + * get marked with SMACK64EXEC and even if the parent + * and child shouldn't be talking the parent still + * may expect to know when the child exits. */ - if (smack_privileged(CAP_MAC_OVERRIDE) || - has_capability(p, CAP_MAC_OVERRIDE)) - rc = 0; - /* we log only if we didn't get overriden */ - out_log: - smk_ad_init(&ad, __func__, LSM_AUDIT_DATA_TASK); - smk_ad_setfield_u_tsk(&ad, p); - smack_log(tsp, sp, MAY_WRITE, rc, &ad); - return rc; + return 0; } /** -- cgit v1.2.3 From 449543b0436a9146b855aad39eab76ae4853e88d Mon Sep 17 00:00:00 2001 From: Rafal Krypa Date: Wed, 11 Jul 2012 17:49:30 +0200 Subject: Smack: implement revoking all rules for a subject label Add /smack/revoke-subject special file. Writing a SMACK label to this file will set the access to '-' for all access rules with that subject label. Targeted for git://git.gitorious.org/smack-next/kernel.git Signed-off-by: Rafal Krypa --- security/smack/smackfs.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) (limited to 'security') diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index b1b768e4049a..99929a50093a 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -49,6 +49,7 @@ enum smk_inos { SMK_LOAD_SELF2 = 15, /* load task specific rules with long labels */ SMK_ACCESS2 = 16, /* make an access check with long labels */ SMK_CIPSO2 = 17, /* load long label -> CIPSO mapping */ + SMK_REVOKE_SUBJ = 18, /* set rules with subject label to '-' */ }; /* @@ -1991,6 +1992,77 @@ static const struct file_operations smk_access2_ops = { .llseek = generic_file_llseek, }; +/** + * smk_write_revoke_subj - write() for /smack/revoke-subject + * @file: file pointer + * @buf: data from user space + * @count: bytes sent + * @ppos: where to start - must be 0 + */ +static ssize_t smk_write_revoke_subj(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + char *data = NULL; + const char *cp = NULL; + struct smack_known *skp; + struct smack_rule *sp; + struct list_head *rule_list; + struct mutex *rule_lock; + int rc = count; + + if (*ppos != 0) + return -EINVAL; + + if (!smack_privileged(CAP_MAC_ADMIN)) + return -EPERM; + + if (count == 0 || count > SMK_LONGLABEL) + return -EINVAL; + + data = kzalloc(count, GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + if (copy_from_user(data, buf, count) != 0) { + rc = -EFAULT; + goto free_out; + } + + cp = smk_parse_smack(data, count); + if (cp == NULL) { + rc = -EINVAL; + goto free_out; + } + + skp = smk_find_entry(cp); + if (skp == NULL) { + rc = -EINVAL; + goto free_out; + } + + rule_list = &skp->smk_rules; + rule_lock = &skp->smk_rules_lock; + + mutex_lock(rule_lock); + + list_for_each_entry_rcu(sp, rule_list, list) + sp->smk_access = 0; + + mutex_unlock(rule_lock); + +free_out: + kfree(data); + kfree(cp); + return rc; +} + +static const struct file_operations smk_revoke_subj_ops = { + .write = smk_write_revoke_subj, + .read = simple_transaction_read, + .release = simple_transaction_release, + .llseek = generic_file_llseek, +}; + /** * smk_fill_super - fill the /smackfs superblock * @sb: the empty superblock @@ -2037,6 +2109,9 @@ static int smk_fill_super(struct super_block *sb, void *data, int silent) "access2", &smk_access2_ops, S_IRUGO|S_IWUGO}, [SMK_CIPSO2] = { "cipso2", &smk_cipso2_ops, S_IRUGO|S_IWUSR}, + [SMK_REVOKE_SUBJ] = { + "revoke-subject", &smk_revoke_subj_ops, + S_IRUGO|S_IWUSR}, /* last one */ {""} }; -- cgit v1.2.3 From 46a2f3b9e99353cc63e15563e8abee71162330f7 Mon Sep 17 00:00:00 2001 From: Casey Schaufler Date: Wed, 22 Aug 2012 11:44:03 -0700 Subject: Smack: setprocattr memory leak fix The data structure allocations being done in prepare_creds are duplicated in smack_setprocattr. This results in the structure allocated in prepare_creds being orphaned and never freed. The duplicate code is removed from smack_setprocattr. Targeted for git://git.gitorious.org/smack-next/kernel.git Signed-off-by: Casey Schaufler --- security/smack/smack_lsm.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) (limited to 'security') diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index ce9273a18165..2874c7316783 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -2684,9 +2684,7 @@ static int smack_getprocattr(struct task_struct *p, char *name, char **value) static int smack_setprocattr(struct task_struct *p, char *name, void *value, size_t size) { - int rc; struct task_smack *tsp; - struct task_smack *oldtsp; struct cred *new; char *newsmack; @@ -2716,21 +2714,13 @@ static int smack_setprocattr(struct task_struct *p, char *name, if (newsmack == smack_known_web.smk_known) return -EPERM; - oldtsp = p->cred->security; new = prepare_creds(); if (new == NULL) return -ENOMEM; - tsp = new_task_smack(newsmack, oldtsp->smk_forked, GFP_KERNEL); - if (tsp == NULL) { - kfree(new); - return -ENOMEM; - } - rc = smk_copy_rules(&tsp->smk_rules, &oldtsp->smk_rules, GFP_KERNEL); - if (rc != 0) - return rc; + tsp = new->security; + tsp->smk_task = newsmack; - new->security = tsp; commit_creds(new); return size; } -- cgit v1.2.3 From 0a72ba7aff26fb6e918cee6d2bbfd289069f10ae Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Wed, 19 Sep 2012 15:32:49 +0300 Subject: ima: change flags container data type IMA audit hashes patches introduced new IMA flags and required space went beyond 8 bits. Currently the only flag is IMA_DIGSIG. This patch use 16 bit short instead of 8 bit char. Without this fix IMA signature will be replaced with hash, which should not happen. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar --- security/integrity/integrity.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 403ba319a06a..e9db763a875e 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -48,7 +48,7 @@ struct integrity_iint_cache { struct rb_node rb_node; /* rooted in integrity_iint_tree */ struct inode *inode; /* back pointer to inode in question */ u64 version; /* track inode changes */ - unsigned char flags; + unsigned short flags; struct evm_ima_xattr_data ima_xattr; enum integrity_status ima_status; enum integrity_status evm_status; -- cgit v1.2.3 From 2db81452931eb51cc739d6e495cf1bd4860c3c99 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Tue, 7 Feb 2012 16:33:13 -0800 Subject: userns: Convert apparmor to use kuid and kgid where appropriate Cc: John Johansen Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- security/apparmor/domain.c | 4 ++-- security/apparmor/file.c | 12 +++++++----- security/apparmor/include/audit.h | 2 +- security/apparmor/include/file.h | 4 ++-- 4 files changed, 12 insertions(+), 10 deletions(-) (limited to 'security') diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index b81ea10a17a3..60f0c76a27d3 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -721,7 +721,7 @@ audit: if (!permtest) error = aa_audit_file(profile, &perms, GFP_KERNEL, OP_CHANGE_HAT, AA_MAY_CHANGEHAT, NULL, - target, 0, info, error); + target, GLOBAL_ROOT_UID, info, error); out: aa_put_profile(hat); @@ -848,7 +848,7 @@ int aa_change_profile(const char *ns_name, const char *hname, bool onexec, audit: if (!permtest) error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, - name, hname, 0, info, error); + name, hname, GLOBAL_ROOT_UID, info, error); aa_put_namespace(ns); aa_put_profile(target); diff --git a/security/apparmor/file.c b/security/apparmor/file.c index cf19d4093ca4..cd21ec5b90af 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -65,7 +65,7 @@ static void audit_file_mask(struct audit_buffer *ab, u32 mask) static void file_audit_cb(struct audit_buffer *ab, void *va) { struct common_audit_data *sa = va; - uid_t fsuid = current_fsuid(); + kuid_t fsuid = current_fsuid(); if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { audit_log_format(ab, " requested_mask="); @@ -76,8 +76,10 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) audit_file_mask(ab, sa->aad->fs.denied); } if (sa->aad->fs.request & AA_AUDIT_FILE_MASK) { - audit_log_format(ab, " fsuid=%d", fsuid); - audit_log_format(ab, " ouid=%d", sa->aad->fs.ouid); + audit_log_format(ab, " fsuid=%d", + from_kuid(&init_user_ns, fsuid)); + audit_log_format(ab, " ouid=%d", + from_kuid(&init_user_ns, sa->aad->fs.ouid)); } if (sa->aad->fs.target) { @@ -103,7 +105,7 @@ static void file_audit_cb(struct audit_buffer *ab, void *va) */ int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, gfp_t gfp, int op, u32 request, const char *name, - const char *target, uid_t ouid, const char *info, int error) + const char *target, kuid_t ouid, const char *info, int error) { int type = AUDIT_APPARMOR_AUTO; struct common_audit_data sa; @@ -201,7 +203,7 @@ static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state, */ perms.kill = 0; - if (current_fsuid() == cond->uid) { + if (uid_eq(current_fsuid(), cond->uid)) { perms.allow = map_old_perms(dfa_user_allow(dfa, state)); perms.audit = map_old_perms(dfa_user_audit(dfa, state)); perms.quiet = map_old_perms(dfa_user_quiet(dfa, state)); diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 4b7e18951aea..69d8cae634e7 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -125,7 +125,7 @@ struct apparmor_audit_data { const char *target; u32 request; u32 denied; - uid_t ouid; + kuid_t ouid; } fs; }; }; diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index f98fd4701d80..967b2deda376 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -71,7 +71,7 @@ struct path; /* need to make conditional which ones are being set */ struct path_cond { - uid_t uid; + kuid_t uid; umode_t mode; }; @@ -146,7 +146,7 @@ static inline u16 dfa_map_xindex(u16 mask) int aa_audit_file(struct aa_profile *profile, struct file_perms *perms, gfp_t gfp, int op, u32 request, const char *name, - const char *target, uid_t ouid, const char *info, int error); + const char *target, kuid_t ouid, const char *info, int error); /** * struct aa_file_rules - components used for file rule permissions -- cgit v1.2.3 From 609fcd1b3a55f99667c61609895c83019b21baad Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Tue, 7 Feb 2012 16:34:10 -0800 Subject: userns: Convert tomoyo to use kuid and kgid where appropriate Acked-by: Tetsuo Handa Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- security/tomoyo/audit.c | 23 ++++++++++++++++------- security/tomoyo/common.c | 4 +++- security/tomoyo/common.h | 4 ++-- security/tomoyo/condition.c | 20 ++++++++++---------- 4 files changed, 31 insertions(+), 20 deletions(-) (limited to 'security') diff --git a/security/tomoyo/audit.c b/security/tomoyo/audit.c index 7ef9fa3e37e0..c1b00375c9ad 100644 --- a/security/tomoyo/audit.c +++ b/security/tomoyo/audit.c @@ -168,9 +168,14 @@ static char *tomoyo_print_header(struct tomoyo_request_info *r) stamp.day, stamp.hour, stamp.min, stamp.sec, r->profile, tomoyo_mode[r->mode], tomoyo_yesno(r->granted), gpid, tomoyo_sys_getpid(), tomoyo_sys_getppid(), - current_uid(), current_gid(), current_euid(), - current_egid(), current_suid(), current_sgid(), - current_fsuid(), current_fsgid()); + from_kuid(&init_user_ns, current_uid()), + from_kgid(&init_user_ns, current_gid()), + from_kuid(&init_user_ns, current_euid()), + from_kgid(&init_user_ns, current_egid()), + from_kuid(&init_user_ns, current_suid()), + from_kgid(&init_user_ns, current_sgid()), + from_kuid(&init_user_ns, current_fsuid()), + from_kgid(&init_user_ns, current_fsgid())); if (!obj) goto no_obj_info; if (!obj->validate_done) { @@ -191,15 +196,19 @@ static char *tomoyo_print_header(struct tomoyo_request_info *r) tomoyo_buffer_len - 1 - pos, " path%u.parent={ uid=%u gid=%u " "ino=%lu perm=0%o }", (i >> 1) + 1, - stat->uid, stat->gid, (unsigned long) - stat->ino, stat->mode & S_IALLUGO); + from_kuid(&init_user_ns, stat->uid), + from_kgid(&init_user_ns, stat->gid), + (unsigned long)stat->ino, + stat->mode & S_IALLUGO); continue; } pos += snprintf(buffer + pos, tomoyo_buffer_len - 1 - pos, " path%u={ uid=%u gid=%u ino=%lu major=%u" " minor=%u perm=0%o type=%s", (i >> 1) + 1, - stat->uid, stat->gid, (unsigned long) - stat->ino, MAJOR(dev), MINOR(dev), + from_kuid(&init_user_ns, stat->uid), + from_kgid(&init_user_ns, stat->gid), + (unsigned long)stat->ino, + MAJOR(dev), MINOR(dev), mode & S_IALLUGO, tomoyo_filetype(mode)); if (S_ISCHR(mode) || S_ISBLK(mode)) { dev = stat->rdev; diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index 2e0f12c62938..f89a0333b813 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -925,7 +925,9 @@ static bool tomoyo_manager(void) if (!tomoyo_policy_loaded) return true; - if (!tomoyo_manage_by_non_root && (task->cred->uid || task->cred->euid)) + if (!tomoyo_manage_by_non_root && + (!uid_eq(task->cred->uid, GLOBAL_ROOT_UID) || + !uid_eq(task->cred->euid, GLOBAL_ROOT_UID))) return false; exe = tomoyo_get_exe(); if (!exe) diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index 75e4dc1c02a0..af010b62d544 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -561,8 +561,8 @@ struct tomoyo_address_group { /* Subset of "struct stat". Used by conditional ACL and audit logs. */ struct tomoyo_mini_stat { - uid_t uid; - gid_t gid; + kuid_t uid; + kgid_t gid; ino_t ino; umode_t mode; dev_t dev; diff --git a/security/tomoyo/condition.c b/security/tomoyo/condition.c index 986330b8c73e..63681e8be628 100644 --- a/security/tomoyo/condition.c +++ b/security/tomoyo/condition.c @@ -813,28 +813,28 @@ bool tomoyo_condition(struct tomoyo_request_info *r, unsigned long value = 0; switch (index) { case TOMOYO_TASK_UID: - value = current_uid(); + value = from_kuid(&init_user_ns, current_uid()); break; case TOMOYO_TASK_EUID: - value = current_euid(); + value = from_kuid(&init_user_ns, current_euid()); break; case TOMOYO_TASK_SUID: - value = current_suid(); + value = from_kuid(&init_user_ns, current_suid()); break; case TOMOYO_TASK_FSUID: - value = current_fsuid(); + value = from_kuid(&init_user_ns, current_fsuid()); break; case TOMOYO_TASK_GID: - value = current_gid(); + value = from_kgid(&init_user_ns, current_gid()); break; case TOMOYO_TASK_EGID: - value = current_egid(); + value = from_kgid(&init_user_ns, current_egid()); break; case TOMOYO_TASK_SGID: - value = current_sgid(); + value = from_kgid(&init_user_ns, current_sgid()); break; case TOMOYO_TASK_FSGID: - value = current_fsgid(); + value = from_kgid(&init_user_ns, current_fsgid()); break; case TOMOYO_TASK_PID: value = tomoyo_sys_getpid(); @@ -970,13 +970,13 @@ bool tomoyo_condition(struct tomoyo_request_info *r, case TOMOYO_PATH2_UID: case TOMOYO_PATH1_PARENT_UID: case TOMOYO_PATH2_PARENT_UID: - value = stat->uid; + value = from_kuid(&init_user_ns, stat->uid); break; case TOMOYO_PATH1_GID: case TOMOYO_PATH2_GID: case TOMOYO_PATH1_PARENT_GID: case TOMOYO_PATH2_PARENT_GID: - value = stat->gid; + value = from_kgid(&init_user_ns, stat->gid); break; case TOMOYO_PATH1_INO: case TOMOYO_PATH2_INO: -- cgit v1.2.3 From 581abc09c2205e05256d7f75410345d5392d5098 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Mon, 20 Aug 2012 00:09:36 -0700 Subject: userns: Convert selinux to use kuid and kgid where appropriate Cc: Stephen Smalley Cc: James Morris Cc: Eric Paris Signed-off-by: "Eric W. Biederman" --- security/selinux/selinuxfs.c | 6 +++--- security/selinux/ss/services.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 298e695d6822..55af8c5b57e6 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -174,7 +174,7 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf, audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_STATUS, "enforcing=%d old_enforcing=%d auid=%u ses=%u", new_value, selinux_enforcing, - audit_get_loginuid(current), + from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); selinux_enforcing = new_value; if (selinux_enforcing) @@ -305,7 +305,7 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf, goto out; audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_STATUS, "selinux=0 auid=%u ses=%u", - audit_get_loginuid(current), + from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); } @@ -551,7 +551,7 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf, out1: audit_log(current->audit_context, GFP_KERNEL, AUDIT_MAC_POLICY_LOAD, "policy loaded auid=%u ses=%u", - audit_get_loginuid(current), + from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); out: mutex_unlock(&sel_mutex); diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c index 4321b8fc8863..b4feecc3fe01 100644 --- a/security/selinux/ss/services.c +++ b/security/selinux/ss/services.c @@ -2440,7 +2440,7 @@ int security_set_bools(int len, int *values) sym_name(&policydb, SYM_BOOLS, i), !!values[i], policydb.bool_val_to_struct[i]->state, - audit_get_loginuid(current), + from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); } if (values[i]) -- cgit v1.2.3 From cf9c93526f4517581a9e8f1c0d9093a4c7748ec6 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Fri, 25 May 2012 18:22:35 -0600 Subject: userns: Convert EVM to deal with kuids and kgids in it's hmac computation Cc: Mimi Zohar Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- security/integrity/evm/evm_crypto.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'security') diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index 49a464f5595b..dfb26918699c 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -106,8 +106,8 @@ static void hmac_add_misc(struct shash_desc *desc, struct inode *inode, memset(&hmac_misc, 0, sizeof hmac_misc); hmac_misc.ino = inode->i_ino; hmac_misc.generation = inode->i_generation; - hmac_misc.uid = inode->i_uid; - hmac_misc.gid = inode->i_gid; + hmac_misc.uid = from_kuid(&init_user_ns, inode->i_uid); + hmac_misc.gid = from_kgid(&init_user_ns, inode->i_gid); hmac_misc.mode = inode->i_mode; crypto_shash_update(desc, (const u8 *)&hmac_misc, sizeof hmac_misc); crypto_shash_final(desc, digest); -- cgit v1.2.3 From 8b94eea4bfb8df693c5b35d08b74f13cfb92f3de Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Fri, 25 May 2012 18:24:12 -0600 Subject: userns: Add user namespace support to IMA Use kuid's in the IMA rules. When reporting the current uid in audit logs use from_kuid to get a usable value. Cc: Mimi Zohar Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- security/integrity/ima/ima_audit.c | 5 +++-- security/integrity/ima/ima_policy.c | 14 +++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima_audit.c b/security/integrity/ima/ima_audit.c index 7a57f6769e9c..c586faae8fd6 100644 --- a/security/integrity/ima/ima_audit.c +++ b/security/integrity/ima/ima_audit.c @@ -39,8 +39,9 @@ void integrity_audit_msg(int audit_msgno, struct inode *inode, ab = audit_log_start(current->audit_context, GFP_KERNEL, audit_msgno); audit_log_format(ab, "pid=%d uid=%u auid=%u ses=%u", - current->pid, current_cred()->uid, - audit_get_loginuid(current), + current->pid, + from_kuid(&init_user_ns, current_cred()->uid), + from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); audit_log_task_context(ab); audit_log_format(ab, " op="); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 1a9583008aae..c84df05180cb 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -39,7 +39,7 @@ struct ima_measure_rule_entry { enum ima_hooks func; int mask; unsigned long fsmagic; - uid_t uid; + kuid_t uid; struct { void *rule; /* LSM file metadata specific */ int type; /* audit type */ @@ -71,7 +71,7 @@ static struct ima_measure_rule_entry default_rules[] = { .flags = IMA_FUNC | IMA_MASK}, {.action = MEASURE,.func = BPRM_CHECK,.mask = MAY_EXEC, .flags = IMA_FUNC | IMA_MASK}, - {.action = MEASURE,.func = FILE_CHECK,.mask = MAY_READ,.uid = 0, + {.action = MEASURE,.func = FILE_CHECK,.mask = MAY_READ,.uid = GLOBAL_ROOT_UID, .flags = IMA_FUNC | IMA_MASK | IMA_UID}, }; @@ -112,7 +112,7 @@ static bool ima_match_rules(struct ima_measure_rule_entry *rule, if ((rule->flags & IMA_FSMAGIC) && rule->fsmagic != inode->i_sb->s_magic) return false; - if ((rule->flags & IMA_UID) && rule->uid != cred->uid) + if ((rule->flags & IMA_UID) && !uid_eq(rule->uid, cred->uid)) return false; for (i = 0; i < MAX_LSM_RULES; i++) { int rc = 0; @@ -277,7 +277,7 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) ab = audit_log_start(NULL, GFP_KERNEL, AUDIT_INTEGRITY_RULE); - entry->uid = -1; + entry->uid = INVALID_UID; entry->action = UNKNOWN; while ((p = strsep(&rule, " \t")) != NULL) { substring_t args[MAX_OPT_ARGS]; @@ -361,15 +361,15 @@ static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) case Opt_uid: ima_log_string(ab, "uid", args[0].from); - if (entry->uid != -1) { + if (uid_valid(entry->uid)) { result = -EINVAL; break; } result = strict_strtoul(args[0].from, 10, &lnum); if (!result) { - entry->uid = (uid_t) lnum; - if (entry->uid != lnum) + entry->uid = make_kuid(current_user_ns(), (uid_t)lnum); + if (!uid_valid(entry->uid) || (((uid_t)lnum) != lnum)) result = -EINVAL; else entry->flags |= IMA_UID; -- cgit v1.2.3 From d2b31ca644fdc8704de3367a6a56a5c958c77f53 Mon Sep 17 00:00:00 2001 From: "Eric W. Biederman" Date: Fri, 1 Jun 2012 16:14:19 -0600 Subject: userns: Teach security_path_chown to take kuids and kgids Don't make the security modules deal with raw user space uid and gids instead pass in a kuid_t and a kgid_t so that security modules only have to deal with internal kernel uids and gids. Cc: Al Viro Cc: James Morris Cc: John Johansen Cc: Kentaro Takeda Cc: Tetsuo Handa Acked-by: Serge Hallyn Signed-off-by: Eric W. Biederman --- security/apparmor/lsm.c | 2 +- security/capability.c | 2 +- security/security.c | 2 +- security/tomoyo/tomoyo.c | 12 +++++++----- 4 files changed, 10 insertions(+), 8 deletions(-) (limited to 'security') diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 8ea39aabe948..8c2a7f6b35e2 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -352,7 +352,7 @@ static int apparmor_path_chmod(struct path *path, umode_t mode) return common_perm_mnt_dentry(OP_CHMOD, path->mnt, path->dentry, AA_MAY_CHMOD); } -static int apparmor_path_chown(struct path *path, uid_t uid, gid_t gid) +static int apparmor_path_chown(struct path *path, kuid_t uid, kgid_t gid) { struct path_cond cond = { path->dentry->d_inode->i_uid, path->dentry->d_inode->i_mode diff --git a/security/capability.c b/security/capability.c index 61095df8b89a..a40aac677c72 100644 --- a/security/capability.c +++ b/security/capability.c @@ -284,7 +284,7 @@ static int cap_path_chmod(struct path *path, umode_t mode) return 0; } -static int cap_path_chown(struct path *path, uid_t uid, gid_t gid) +static int cap_path_chown(struct path *path, kuid_t uid, kgid_t gid) { return 0; } diff --git a/security/security.c b/security/security.c index 860aeb349cb3..f9a2f2ef2454 100644 --- a/security/security.c +++ b/security/security.c @@ -434,7 +434,7 @@ int security_path_chmod(struct path *path, umode_t mode) return security_ops->path_chmod(path, mode); } -int security_path_chown(struct path *path, uid_t uid, gid_t gid) +int security_path_chown(struct path *path, kuid_t uid, kgid_t gid) { if (unlikely(IS_PRIVATE(path->dentry->d_inode))) return 0; diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index c2d04a50f76a..d88eb3a046ed 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -373,13 +373,15 @@ static int tomoyo_path_chmod(struct path *path, umode_t mode) * * Returns 0 on success, negative value otherwise. */ -static int tomoyo_path_chown(struct path *path, uid_t uid, gid_t gid) +static int tomoyo_path_chown(struct path *path, kuid_t uid, kgid_t gid) { int error = 0; - if (uid != (uid_t) -1) - error = tomoyo_path_number_perm(TOMOYO_TYPE_CHOWN, path, uid); - if (!error && gid != (gid_t) -1) - error = tomoyo_path_number_perm(TOMOYO_TYPE_CHGRP, path, gid); + if (uid_valid(uid)) + error = tomoyo_path_number_perm(TOMOYO_TYPE_CHOWN, path, + from_kuid(&init_user_ns, uid)); + if (!error && gid_valid(gid)) + error = tomoyo_path_number_perm(TOMOYO_TYPE_CHGRP, path, + from_kgid(&init_user_ns, gid)); return error; } -- cgit v1.2.3 From ee97cd872d08b8623076f2a63ffb872d0884411a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 21 Aug 2012 12:26:45 -0400 Subject: switch flush_unauthorized_files() to replace_fd() Signed-off-by: Al Viro --- security/selinux/hooks.c | 47 +++++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 6c77f63c7591..00b50113642d 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2126,8 +2126,6 @@ static inline void flush_unauthorized_files(const struct cred *cred, spin_lock(&files->file_lock); for (;;) { unsigned long set, i; - int fd; - j++; i = j * BITS_PER_LONG; fdt = files_fdtable(files); @@ -2138,38 +2136,23 @@ static inline void flush_unauthorized_files(const struct cred *cred, continue; spin_unlock(&files->file_lock); for ( ; set ; i++, set >>= 1) { - if (set & 1) { - file = fget(i); - if (!file) - continue; - if (file_has_perm(cred, - file, - file_to_av(file))) { - sys_close(i); - fd = get_unused_fd(); - if (fd != i) { - if (fd >= 0) - put_unused_fd(fd); - fput(file); - continue; - } - if (devnull) { - get_file(devnull); - } else { - devnull = dentry_open( - &selinux_null, - O_RDWR, cred); - if (IS_ERR(devnull)) { - devnull = NULL; - put_unused_fd(fd); - fput(file); - continue; - } - } - fd_install(fd, devnull); + if (!(set & 1)) + continue; + file = fget(i); + if (!file) + continue; + if (file_has_perm(cred, file, file_to_av(file))) { + if (devnull) { + get_file(devnull); + } else { + devnull = dentry_open(&selinux_null, + O_RDWR, cred); + if (IS_ERR(devnull)) + devnull = NULL; } - fput(file); + replace_fd(i, devnull, 0); } + fput(file); } spin_lock(&files->file_lock); -- cgit v1.2.3 From c3c073f808b22dfae15ef8412b6f7b998644139a Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 21 Aug 2012 22:32:06 -0400 Subject: new helper: iterate_fd() iterates through the opened files in given descriptor table, calling a supplied function; we stop once non-zero is returned. Callback gets struct file *, descriptor number and const void * argument passed to iterator. It is called with files->file_lock held, so it is not allowed to block. tty_io, netprio_cgroup and selinux flush_unauthorized_files() converted to its use. Signed-off-by: Al Viro --- security/selinux/hooks.c | 57 +++++++++++++++++++----------------------------- 1 file changed, 22 insertions(+), 35 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 00b50113642d..4dfbcea10eb7 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2088,15 +2088,19 @@ static int selinux_bprm_secureexec(struct linux_binprm *bprm) return (atsecure || cap_bprm_secureexec(bprm)); } +static int match_file(const void *p, struct file *file, unsigned fd) +{ + return file_has_perm(p, file, file_to_av(file)) ? fd + 1 : 0; +} + /* Derived from fs/exec.c:flush_old_files. */ static inline void flush_unauthorized_files(const struct cred *cred, struct files_struct *files) { struct file *file, *devnull = NULL; struct tty_struct *tty; - struct fdtable *fdt; - long j = -1; int drop_tty = 0; + unsigned n; tty = get_current_tty(); if (tty) { @@ -2123,41 +2127,24 @@ static inline void flush_unauthorized_files(const struct cred *cred, no_tty(); /* Revalidate access to inherited open files. */ - spin_lock(&files->file_lock); - for (;;) { - unsigned long set, i; - j++; - i = j * BITS_PER_LONG; - fdt = files_fdtable(files); - if (i >= fdt->max_fds) - break; - set = fdt->open_fds[j]; - if (!set) - continue; - spin_unlock(&files->file_lock); - for ( ; set ; i++, set >>= 1) { - if (!(set & 1)) - continue; - file = fget(i); - if (!file) - continue; - if (file_has_perm(cred, file, file_to_av(file))) { - if (devnull) { - get_file(devnull); - } else { - devnull = dentry_open(&selinux_null, - O_RDWR, cred); - if (IS_ERR(devnull)) - devnull = NULL; - } - replace_fd(i, devnull, 0); - } - fput(file); - } - spin_lock(&files->file_lock); + n = iterate_fd(files, 0, match_file, cred); + if (!n) /* none found? */ + return; + devnull = dentry_open(&selinux_null, O_RDWR, cred); + if (!IS_ERR(devnull)) { + /* replace all the matching ones with this */ + do { + get_file(devnull); + replace_fd(n - 1, devnull, 0); + } while ((n = iterate_fd(files, n, match_file, cred)) != 0); + fput(devnull); + } else { + /* just close all the matching ones */ + do { + replace_fd(n - 1, NULL, 0); + } while ((n = iterate_fd(files, n, match_file, cred)) != 0); } - spin_unlock(&files->file_lock); } /* -- cgit v1.2.3 From cb0942b81249798e15c3f04eee2946ef543e8115 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Mon, 27 Aug 2012 14:48:26 -0400 Subject: make get_file() return its argument simplifies a bunch of callers... Signed-off-by: Al Viro --- security/selinux/hooks.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 4dfbcea10eb7..651d8456611a 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2135,8 +2135,7 @@ static inline void flush_unauthorized_files(const struct cred *cred, if (!IS_ERR(devnull)) { /* replace all the matching ones with this */ do { - get_file(devnull); - replace_fd(n - 1, devnull, 0); + replace_fd(n - 1, get_file(devnull), 0); } while ((n = iterate_fd(files, n, match_file, cred)) != 0); fput(devnull); } else { -- cgit v1.2.3 From 8a1ab3155c2ac7fbe5f2038d6e26efeb607a1498 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 4 Oct 2012 18:20:15 +0100 Subject: UAPI: (Scripted) Disintegrate include/asm-generic Signed-off-by: David Howells Acked-by: Arnd Bergmann Acked-by: Thomas Gleixner Acked-by: Michael Kerrisk Acked-by: Paul E. McKenney Acked-by: Dave Jones --- security/apparmor/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 806bd19af7f2..7b3021cebbea 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -60,6 +60,6 @@ $(obj)/resource.o : $(obj)/rlim_names.h $(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ $(src)/Makefile $(call cmd,make-caps) -$(obj)/rlim_names.h : $(srctree)/include/asm-generic/resource.h \ +$(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ $(src)/Makefile $(call cmd,make-rlim) -- cgit v1.2.3 From d26e1936227b538a1691b978566ef269aef10853 Mon Sep 17 00:00:00 2001 From: Dmitry Kasatkin Date: Thu, 27 Sep 2012 18:26:53 +0300 Subject: ima: fix bug in argument order mask argument goes first, then func, like ima_must_measure and ima_get_action. ima_inode_post_setattr() assumes that. Signed-off-by: Dmitry Kasatkin Signed-off-by: Mimi Zohar Signed-off-by: James Morris --- security/integrity/ima/ima.h | 6 +++--- security/integrity/ima/ima_appraise.c | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'security') diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 8180adde10b7..6ee8826662cc 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -143,7 +143,7 @@ void ima_delete_rules(void); #ifdef CONFIG_IMA_APPRAISE int ima_appraise_measurement(struct integrity_iint_cache *iint, struct file *file, const unsigned char *filename); -int ima_must_appraise(struct inode *inode, enum ima_hooks func, int mask); +int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func); void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file); #else @@ -154,8 +154,8 @@ static inline int ima_appraise_measurement(struct integrity_iint_cache *iint, return INTEGRITY_UNKNOWN; } -static inline int ima_must_appraise(struct inode *inode, - enum ima_hooks func, int mask) +static inline int ima_must_appraise(struct inode *inode, int mask, + enum ima_hooks func) { return 0; } diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 0aa43bde441c..bdc8ba1d1d27 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -34,7 +34,7 @@ __setup("ima_appraise=", default_appraise_setup); * * Return 1 to appraise */ -int ima_must_appraise(struct inode *inode, enum ima_hooks func, int mask) +int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) { if (!ima_appraise) return 0; -- cgit v1.2.3 From 66b8ef67756b3051bf42a077a82c3c5c279caa5b Mon Sep 17 00:00:00 2001 From: Aristeu Rozanski Date: Thu, 4 Oct 2012 17:15:13 -0700 Subject: device_cgroup: add "deny_all" in dev_cgroup structure deny_all will determine if the default policy is to deny all device access unless for the ones in the exception list. This variable will be used in the next patches to convert device_cgroup internally into a default policy + rules. Signed-off-by: Aristeu Rozanski Cc: Tejun Heo Cc: Li Zefan Cc: James Morris Cc: Pavel Emelyanov Acked-by: Serge E. Hallyn Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/device_cgroup.c | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 4b877a92a7ea..e3ce02a00ffc 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -42,6 +42,7 @@ struct dev_whitelist_item { struct dev_cgroup { struct cgroup_subsys_state css; struct list_head whitelist; + bool deny_all; }; static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) @@ -178,12 +179,14 @@ static struct cgroup_subsys_state *devcgroup_create(struct cgroup *cgroup) wh->minor = wh->major = ~0; wh->type = DEV_ALL; wh->access = ACC_MASK; + dev_cgroup->deny_all = false; list_add(&wh->list, &dev_cgroup->whitelist); } else { parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); mutex_lock(&devcgroup_mutex); ret = dev_whitelist_copy(&dev_cgroup->whitelist, &parent_dev_cgroup->whitelist); + dev_cgroup->deny_all = parent_dev_cgroup->deny_all; mutex_unlock(&devcgroup_mutex); if (ret) { kfree(dev_cgroup); @@ -409,9 +412,11 @@ handle: case DEVCG_ALLOW: if (!parent_has_perm(devcgroup, &wh)) return -EPERM; + devcgroup->deny_all = false; return dev_whitelist_add(devcgroup, &wh); case DEVCG_DENY: dev_whitelist_rm(devcgroup, &wh); + devcgroup->deny_all = true; break; default: return -EINVAL; -- cgit v1.2.3 From 868539a3b671e0f736ddd11b67bf1dc3d8a5a921 Mon Sep 17 00:00:00 2001 From: Aristeu Rozanski Date: Thu, 4 Oct 2012 17:15:15 -0700 Subject: device_cgroup: introduce dev_whitelist_clean() This function cleans all the items in a whitelist and will be used by the next patches. Signed-off-by: Aristeu Rozanski Cc: Tejun Heo Cc: Li Zefan Cc: James Morris Cc: Pavel Emelyanov Acked-by: Serge E. Hallyn Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/device_cgroup.c | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index e3ce02a00ffc..5b36b62e9ff4 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -154,6 +154,22 @@ remove: } } +/** + * dev_whitelist_clean - frees all entries of the whitelist + * @dev_cgroup: dev_cgroup with the whitelist to be cleaned + * + * called under devcgroup_mutex + */ +static void dev_whitelist_clean(struct dev_cgroup *dev_cgroup) +{ + struct dev_whitelist_item *wh, *tmp; + + list_for_each_entry_safe(wh, tmp, &dev_cgroup->whitelist, list) { + list_del(&wh->list); + kfree(wh); + } +} + /* * called from kernel/cgroup.c with cgroup_lock() held. */ @@ -200,13 +216,9 @@ static struct cgroup_subsys_state *devcgroup_create(struct cgroup *cgroup) static void devcgroup_destroy(struct cgroup *cgroup) { struct dev_cgroup *dev_cgroup; - struct dev_whitelist_item *wh, *tmp; dev_cgroup = cgroup_to_devcgroup(cgroup); - list_for_each_entry_safe(wh, tmp, &dev_cgroup->whitelist, list) { - list_del(&wh->list); - kfree(wh); - } + dev_whitelist_clean(dev_cgroup); kfree(dev_cgroup); } -- cgit v1.2.3 From ad676077a2ae4af4bb6627486ce19ccce04f1efe Mon Sep 17 00:00:00 2001 From: Aristeu Rozanski Date: Thu, 4 Oct 2012 17:15:17 -0700 Subject: device_cgroup: convert device_cgroup internally to policy + exceptions The original model of device_cgroup is having a whitelist where all the allowed devices are listed. The problem with this approach is that is impossible to have the case of allowing everything but few devices. The reason for that lies in the way the whitelist is handled internally: since there's only a whitelist, the "all devices" entry would have to be removed and replaced by the entire list of possible devices but the ones that are being denied. Since dev_t is 32 bits long, representing the allowed devices as a bitfield is not memory efficient. This patch replaces the "whitelist" by a "exceptions" list and the default policy is kept as "deny_all" variable in dev_cgroup structure. The current interface determines that whenever "a" is written to devices.allow or devices.deny, the entry masking all devices will be added or removed, respectively. This behavior is kept and it's what will determine the default policy: # cat devices.list a *:* rwm # echo a >devices.deny # cat devices.list # echo a >devices.allow # cat devices.list a *:* rwm The interface is also preserved. For example, if one wants to block only access to /dev/null: # ls -l /dev/null crw-rw-rw- 1 root root 1, 3 Jul 24 16:17 /dev/null # echo a >devices.allow # echo "c 1:3 rwm" >devices.deny # cat /dev/null cat: /dev/null: Operation not permitted # echo >/dev/null bash: /dev/null: Operation not permitted mknod /tmp/null c 1 3 mknod: `/tmp/null': Operation not permitted # echo "c 1:3 r" >devices.allow # cat /dev/null # echo >/dev/null bash: /dev/null: Operation not permitted mknod /tmp/null c 1 3 mknod: `/tmp/null': Operation not permitted # echo "c 1:3 rw" >devices.allow # echo >/dev/null # cat /dev/null # mknod /tmp/null c 1 3 mknod: `/tmp/null': Operation not permitted # echo "c 1:3 rwm" >devices.allow # echo >/dev/null # cat /dev/null # mknod /tmp/null c 1 3 # Note that I didn't rename the functions/variables in this patch, but in the next one to make reviewing easier. Signed-off-by: Aristeu Rozanski Cc: Tejun Heo Cc: Li Zefan Cc: James Morris Cc: Pavel Emelyanov Acked-by: Serge E. Hallyn Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/device_cgroup.c | 232 +++++++++++++++++++++++++++-------------------- 1 file changed, 134 insertions(+), 98 deletions(-) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 5b36b62e9ff4..91cf803c3431 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -96,7 +96,6 @@ free_and_exit: return -ENOMEM; } -/* Stupid prototype - don't bother combining existing entries */ /* * called under devcgroup_mutex */ @@ -136,16 +135,13 @@ static void dev_whitelist_rm(struct dev_cgroup *dev_cgroup, struct dev_whitelist_item *walk, *tmp; list_for_each_entry_safe(walk, tmp, &dev_cgroup->whitelist, list) { - if (walk->type == DEV_ALL) - goto remove; if (walk->type != wh->type) continue; - if (walk->major != ~0 && walk->major != wh->major) + if (walk->major != wh->major) continue; - if (walk->minor != ~0 && walk->minor != wh->minor) + if (walk->minor != wh->minor) continue; -remove: walk->access &= ~wh->access; if (!walk->access) { list_del_rcu(&walk->list); @@ -185,19 +181,9 @@ static struct cgroup_subsys_state *devcgroup_create(struct cgroup *cgroup) INIT_LIST_HEAD(&dev_cgroup->whitelist); parent_cgroup = cgroup->parent; - if (parent_cgroup == NULL) { - struct dev_whitelist_item *wh; - wh = kmalloc(sizeof(*wh), GFP_KERNEL); - if (!wh) { - kfree(dev_cgroup); - return ERR_PTR(-ENOMEM); - } - wh->minor = wh->major = ~0; - wh->type = DEV_ALL; - wh->access = ACC_MASK; + if (parent_cgroup == NULL) dev_cgroup->deny_all = false; - list_add(&wh->list, &dev_cgroup->whitelist); - } else { + else { parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); mutex_lock(&devcgroup_mutex); ret = dev_whitelist_copy(&dev_cgroup->whitelist, @@ -268,33 +254,48 @@ static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; rcu_read_lock(); - list_for_each_entry_rcu(wh, &devcgroup->whitelist, list) { - set_access(acc, wh->access); - set_majmin(maj, wh->major); - set_majmin(min, wh->minor); - seq_printf(m, "%c %s:%s %s\n", type_to_char(wh->type), + /* + * To preserve the compatibility: + * - Only show the "all devices" when the default policy is to allow + * - List the exceptions in case the default policy is to deny + * This way, the file remains as a "whitelist of devices" + */ + if (devcgroup->deny_all == false) { + set_access(acc, ACC_MASK); + set_majmin(maj, ~0); + set_majmin(min, ~0); + seq_printf(m, "%c %s:%s %s\n", type_to_char(DEV_ALL), maj, min, acc); + } else { + list_for_each_entry_rcu(wh, &devcgroup->whitelist, list) { + set_access(acc, wh->access); + set_majmin(maj, wh->major); + set_majmin(min, wh->minor); + seq_printf(m, "%c %s:%s %s\n", type_to_char(wh->type), + maj, min, acc); + } } rcu_read_unlock(); return 0; } -/* - * may_access_whitelist: - * does the access granted to dev_cgroup c contain the access - * requested in whitelist item refwh. - * return 1 if yes, 0 if no. - * call with devcgroup_mutex held +/** + * may_access_whitelist - verifies if a new rule is part of what is allowed + * by a dev cgroup based on the default policy + + * exceptions. This is used to make sure a child cgroup + * won't have more privileges than its parent or to + * verify if a certain access is allowed. + * @dev_cgroup: dev cgroup to be tested against + * @refwh: new rule */ -static int may_access_whitelist(struct dev_cgroup *c, - struct dev_whitelist_item *refwh) +static int may_access_whitelist(struct dev_cgroup *dev_cgroup, + struct dev_whitelist_item *refwh) { struct dev_whitelist_item *whitem; + bool match = false; - list_for_each_entry(whitem, &c->whitelist, list) { - if (whitem->type & DEV_ALL) - return 1; + list_for_each_entry(whitem, &dev_cgroup->whitelist, list) { if ((refwh->type & DEV_BLOCK) && !(whitem->type & DEV_BLOCK)) continue; if ((refwh->type & DEV_CHAR) && !(whitem->type & DEV_CHAR)) @@ -305,8 +306,21 @@ static int may_access_whitelist(struct dev_cgroup *c, continue; if (refwh->access & (~whitem->access)) continue; - return 1; + match = true; + break; } + + /* + * In two cases we'll consider this new rule valid: + * - the dev cgroup has its default policy to allow + exception list: + * the new rule should *not* match any of the exceptions + * (!deny_all, !match) + * - the dev cgroup has its default policy to deny + exception list: + * the new rule *should* match the exceptions + * (deny_all, match) + */ + if (dev_cgroup->deny_all == match) + return 1; return 0; } @@ -356,11 +370,21 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, switch (*b) { case 'a': - wh.type = DEV_ALL; - wh.access = ACC_MASK; - wh.major = ~0; - wh.minor = ~0; - goto handle; + switch (filetype) { + case DEVCG_ALLOW: + if (!parent_has_perm(devcgroup, &wh)) + return -EPERM; + dev_whitelist_clean(devcgroup); + devcgroup->deny_all = false; + break; + case DEVCG_DENY: + dev_whitelist_clean(devcgroup); + devcgroup->deny_all = true; + break; + default: + return -EINVAL; + } + return 0; case 'b': wh.type = DEV_BLOCK; break; @@ -419,17 +443,31 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, } } -handle: switch (filetype) { case DEVCG_ALLOW: if (!parent_has_perm(devcgroup, &wh)) return -EPERM; - devcgroup->deny_all = false; + /* + * If the default policy is to allow by default, try to remove + * an matching exception instead. And be silent about it: we + * don't want to break compatibility + */ + if (devcgroup->deny_all == false) { + dev_whitelist_rm(devcgroup, &wh); + return 0; + } return dev_whitelist_add(devcgroup, &wh); case DEVCG_DENY: - dev_whitelist_rm(devcgroup, &wh); - devcgroup->deny_all = true; - break; + /* + * If the default policy is to deny by default, try to remove + * an matching exception instead. And be silent about it: we + * don't want to break compatibility + */ + if (devcgroup->deny_all == true) { + dev_whitelist_rm(devcgroup, &wh); + return 0; + } + return dev_whitelist_add(devcgroup, &wh); default: return -EINVAL; } @@ -485,73 +523,71 @@ struct cgroup_subsys devices_subsys = { .broken_hierarchy = true, }; -int __devcgroup_inode_permission(struct inode *inode, int mask) +/** + * __devcgroup_check_permission - checks if an inode operation is permitted + * @dev_cgroup: the dev cgroup to be tested against + * @type: device type + * @major: device major number + * @minor: device minor number + * @access: combination of ACC_WRITE, ACC_READ and ACC_MKNOD + * + * returns 0 on success, -EPERM case the operation is not permitted + */ +static int __devcgroup_check_permission(struct dev_cgroup *dev_cgroup, + short type, u32 major, u32 minor, + short access) { - struct dev_cgroup *dev_cgroup; - struct dev_whitelist_item *wh; - - rcu_read_lock(); + struct dev_whitelist_item wh; + int rc; - dev_cgroup = task_devcgroup(current); + memset(&wh, 0, sizeof(wh)); + wh.type = type; + wh.major = major; + wh.minor = minor; + wh.access = access; - list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) { - if (wh->type & DEV_ALL) - goto found; - if ((wh->type & DEV_BLOCK) && !S_ISBLK(inode->i_mode)) - continue; - if ((wh->type & DEV_CHAR) && !S_ISCHR(inode->i_mode)) - continue; - if (wh->major != ~0 && wh->major != imajor(inode)) - continue; - if (wh->minor != ~0 && wh->minor != iminor(inode)) - continue; + rcu_read_lock(); + rc = may_access_whitelist(dev_cgroup, &wh); + rcu_read_unlock(); - if ((mask & MAY_WRITE) && !(wh->access & ACC_WRITE)) - continue; - if ((mask & MAY_READ) && !(wh->access & ACC_READ)) - continue; -found: - rcu_read_unlock(); - return 0; - } + if (!rc) + return -EPERM; - rcu_read_unlock(); + return 0; +} - return -EPERM; +int __devcgroup_inode_permission(struct inode *inode, int mask) +{ + struct dev_cgroup *dev_cgroup = task_devcgroup(current); + short type, access = 0; + + if (S_ISBLK(inode->i_mode)) + type = DEV_BLOCK; + if (S_ISCHR(inode->i_mode)) + type = DEV_CHAR; + if (mask & MAY_WRITE) + access |= ACC_WRITE; + if (mask & MAY_READ) + access |= ACC_READ; + + return __devcgroup_check_permission(dev_cgroup, type, imajor(inode), + iminor(inode), access); } int devcgroup_inode_mknod(int mode, dev_t dev) { - struct dev_cgroup *dev_cgroup; - struct dev_whitelist_item *wh; + struct dev_cgroup *dev_cgroup = task_devcgroup(current); + short type; if (!S_ISBLK(mode) && !S_ISCHR(mode)) return 0; - rcu_read_lock(); - - dev_cgroup = task_devcgroup(current); - - list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) { - if (wh->type & DEV_ALL) - goto found; - if ((wh->type & DEV_BLOCK) && !S_ISBLK(mode)) - continue; - if ((wh->type & DEV_CHAR) && !S_ISCHR(mode)) - continue; - if (wh->major != ~0 && wh->major != MAJOR(dev)) - continue; - if (wh->minor != ~0 && wh->minor != MINOR(dev)) - continue; - - if (!(wh->access & ACC_MKNOD)) - continue; -found: - rcu_read_unlock(); - return 0; - } + if (S_ISBLK(mode)) + type = DEV_BLOCK; + else + type = DEV_CHAR; - rcu_read_unlock(); + return __devcgroup_check_permission(dev_cgroup, type, MAJOR(dev), + MINOR(dev), ACC_MKNOD); - return -EPERM; } -- cgit v1.2.3 From db9aeca97a58563e1ab927d157c9b5048f233e73 Mon Sep 17 00:00:00 2001 From: Aristeu Rozanski Date: Thu, 4 Oct 2012 17:15:20 -0700 Subject: device_cgroup: rename whitelist to exception list This patch replaces the "whitelist" usage in the code and comments and replace them by exception list related information. Signed-off-by: Aristeu Rozanski Cc: Tejun Heo Cc: Li Zefan Cc: James Morris Cc: Pavel Emelyanov Acked-by: Serge E. Hallyn Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/device_cgroup.c | 198 +++++++++++++++++++++++------------------------ 1 file changed, 99 insertions(+), 99 deletions(-) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 91cf803c3431..44dfc415a379 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -26,12 +26,12 @@ static DEFINE_MUTEX(devcgroup_mutex); /* - * whitelist locking rules: + * exception list locking rules: * hold devcgroup_mutex for update/read. * hold rcu_read_lock() for read. */ -struct dev_whitelist_item { +struct dev_exception_item { u32 major, minor; short type; short access; @@ -41,7 +41,7 @@ struct dev_whitelist_item { struct dev_cgroup { struct cgroup_subsys_state css; - struct list_head whitelist; + struct list_head exceptions; bool deny_all; }; @@ -75,12 +75,12 @@ static int devcgroup_can_attach(struct cgroup *new_cgrp, /* * called under devcgroup_mutex */ -static int dev_whitelist_copy(struct list_head *dest, struct list_head *orig) +static int dev_exceptions_copy(struct list_head *dest, struct list_head *orig) { - struct dev_whitelist_item *wh, *tmp, *new; + struct dev_exception_item *ex, *tmp, *new; - list_for_each_entry(wh, orig, list) { - new = kmemdup(wh, sizeof(*wh), GFP_KERNEL); + list_for_each_entry(ex, orig, list) { + new = kmemdup(ex, sizeof(*ex), GFP_KERNEL); if (!new) goto free_and_exit; list_add_tail(&new->list, dest); @@ -89,9 +89,9 @@ static int dev_whitelist_copy(struct list_head *dest, struct list_head *orig) return 0; free_and_exit: - list_for_each_entry_safe(wh, tmp, dest, list) { - list_del(&wh->list); - kfree(wh); + list_for_each_entry_safe(ex, tmp, dest, list) { + list_del(&ex->list); + kfree(ex); } return -ENOMEM; } @@ -99,50 +99,50 @@ free_and_exit: /* * called under devcgroup_mutex */ -static int dev_whitelist_add(struct dev_cgroup *dev_cgroup, - struct dev_whitelist_item *wh) +static int dev_exception_add(struct dev_cgroup *dev_cgroup, + struct dev_exception_item *ex) { - struct dev_whitelist_item *whcopy, *walk; + struct dev_exception_item *excopy, *walk; - whcopy = kmemdup(wh, sizeof(*wh), GFP_KERNEL); - if (!whcopy) + excopy = kmemdup(ex, sizeof(*ex), GFP_KERNEL); + if (!excopy) return -ENOMEM; - list_for_each_entry(walk, &dev_cgroup->whitelist, list) { - if (walk->type != wh->type) + list_for_each_entry(walk, &dev_cgroup->exceptions, list) { + if (walk->type != ex->type) continue; - if (walk->major != wh->major) + if (walk->major != ex->major) continue; - if (walk->minor != wh->minor) + if (walk->minor != ex->minor) continue; - walk->access |= wh->access; - kfree(whcopy); - whcopy = NULL; + walk->access |= ex->access; + kfree(excopy); + excopy = NULL; } - if (whcopy != NULL) - list_add_tail_rcu(&whcopy->list, &dev_cgroup->whitelist); + if (excopy != NULL) + list_add_tail_rcu(&excopy->list, &dev_cgroup->exceptions); return 0; } /* * called under devcgroup_mutex */ -static void dev_whitelist_rm(struct dev_cgroup *dev_cgroup, - struct dev_whitelist_item *wh) +static void dev_exception_rm(struct dev_cgroup *dev_cgroup, + struct dev_exception_item *ex) { - struct dev_whitelist_item *walk, *tmp; + struct dev_exception_item *walk, *tmp; - list_for_each_entry_safe(walk, tmp, &dev_cgroup->whitelist, list) { - if (walk->type != wh->type) + list_for_each_entry_safe(walk, tmp, &dev_cgroup->exceptions, list) { + if (walk->type != ex->type) continue; - if (walk->major != wh->major) + if (walk->major != ex->major) continue; - if (walk->minor != wh->minor) + if (walk->minor != ex->minor) continue; - walk->access &= ~wh->access; + walk->access &= ~ex->access; if (!walk->access) { list_del_rcu(&walk->list); kfree_rcu(walk, rcu); @@ -151,18 +151,18 @@ static void dev_whitelist_rm(struct dev_cgroup *dev_cgroup, } /** - * dev_whitelist_clean - frees all entries of the whitelist - * @dev_cgroup: dev_cgroup with the whitelist to be cleaned + * dev_exception_clean - frees all entries of the exception list + * @dev_cgroup: dev_cgroup with the exception list to be cleaned * * called under devcgroup_mutex */ -static void dev_whitelist_clean(struct dev_cgroup *dev_cgroup) +static void dev_exception_clean(struct dev_cgroup *dev_cgroup) { - struct dev_whitelist_item *wh, *tmp; + struct dev_exception_item *ex, *tmp; - list_for_each_entry_safe(wh, tmp, &dev_cgroup->whitelist, list) { - list_del(&wh->list); - kfree(wh); + list_for_each_entry_safe(ex, tmp, &dev_cgroup->exceptions, list) { + list_del(&ex->list); + kfree(ex); } } @@ -178,7 +178,7 @@ static struct cgroup_subsys_state *devcgroup_create(struct cgroup *cgroup) dev_cgroup = kzalloc(sizeof(*dev_cgroup), GFP_KERNEL); if (!dev_cgroup) return ERR_PTR(-ENOMEM); - INIT_LIST_HEAD(&dev_cgroup->whitelist); + INIT_LIST_HEAD(&dev_cgroup->exceptions); parent_cgroup = cgroup->parent; if (parent_cgroup == NULL) @@ -186,8 +186,8 @@ static struct cgroup_subsys_state *devcgroup_create(struct cgroup *cgroup) else { parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); mutex_lock(&devcgroup_mutex); - ret = dev_whitelist_copy(&dev_cgroup->whitelist, - &parent_dev_cgroup->whitelist); + ret = dev_exceptions_copy(&dev_cgroup->exceptions, + &parent_dev_cgroup->exceptions); dev_cgroup->deny_all = parent_dev_cgroup->deny_all; mutex_unlock(&devcgroup_mutex); if (ret) { @@ -204,7 +204,7 @@ static void devcgroup_destroy(struct cgroup *cgroup) struct dev_cgroup *dev_cgroup; dev_cgroup = cgroup_to_devcgroup(cgroup); - dev_whitelist_clean(dev_cgroup); + dev_exception_clean(dev_cgroup); kfree(dev_cgroup); } @@ -250,7 +250,7 @@ static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, struct seq_file *m) { struct dev_cgroup *devcgroup = cgroup_to_devcgroup(cgroup); - struct dev_whitelist_item *wh; + struct dev_exception_item *ex; char maj[MAJMINLEN], min[MAJMINLEN], acc[ACCLEN]; rcu_read_lock(); @@ -267,11 +267,11 @@ static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, seq_printf(m, "%c %s:%s %s\n", type_to_char(DEV_ALL), maj, min, acc); } else { - list_for_each_entry_rcu(wh, &devcgroup->whitelist, list) { - set_access(acc, wh->access); - set_majmin(maj, wh->major); - set_majmin(min, wh->minor); - seq_printf(m, "%c %s:%s %s\n", type_to_char(wh->type), + list_for_each_entry_rcu(ex, &devcgroup->exceptions, list) { + set_access(acc, ex->access); + set_majmin(maj, ex->major); + set_majmin(min, ex->minor); + seq_printf(m, "%c %s:%s %s\n", type_to_char(ex->type), maj, min, acc); } } @@ -281,42 +281,42 @@ static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, } /** - * may_access_whitelist - verifies if a new rule is part of what is allowed - * by a dev cgroup based on the default policy + - * exceptions. This is used to make sure a child cgroup - * won't have more privileges than its parent or to - * verify if a certain access is allowed. + * may_access - verifies if a new exception is part of what is allowed + * by a dev cgroup based on the default policy + + * exceptions. This is used to make sure a child cgroup + * won't have more privileges than its parent or to + * verify if a certain access is allowed. * @dev_cgroup: dev cgroup to be tested against - * @refwh: new rule + * @refex: new exception */ -static int may_access_whitelist(struct dev_cgroup *dev_cgroup, - struct dev_whitelist_item *refwh) +static int may_access(struct dev_cgroup *dev_cgroup, + struct dev_exception_item *refex) { - struct dev_whitelist_item *whitem; + struct dev_exception_item *ex; bool match = false; - list_for_each_entry(whitem, &dev_cgroup->whitelist, list) { - if ((refwh->type & DEV_BLOCK) && !(whitem->type & DEV_BLOCK)) + list_for_each_entry(ex, &dev_cgroup->exceptions, list) { + if ((refex->type & DEV_BLOCK) && !(ex->type & DEV_BLOCK)) continue; - if ((refwh->type & DEV_CHAR) && !(whitem->type & DEV_CHAR)) + if ((refex->type & DEV_CHAR) && !(ex->type & DEV_CHAR)) continue; - if (whitem->major != ~0 && whitem->major != refwh->major) + if (ex->major != ~0 && ex->major != refex->major) continue; - if (whitem->minor != ~0 && whitem->minor != refwh->minor) + if (ex->minor != ~0 && ex->minor != refex->minor) continue; - if (refwh->access & (~whitem->access)) + if (refex->access & (~ex->access)) continue; match = true; break; } /* - * In two cases we'll consider this new rule valid: + * In two cases we'll consider this new exception valid: * - the dev cgroup has its default policy to allow + exception list: - * the new rule should *not* match any of the exceptions + * the new exception should *not* match any of the exceptions * (!deny_all, !match) * - the dev cgroup has its default policy to deny + exception list: - * the new rule *should* match the exceptions + * the new exception *should* match the exceptions * (deny_all, match) */ if (dev_cgroup->deny_all == match) @@ -326,11 +326,11 @@ static int may_access_whitelist(struct dev_cgroup *dev_cgroup, /* * parent_has_perm: - * when adding a new allow rule to a device whitelist, the rule + * when adding a new allow rule to a device exception list, the rule * must be allowed in the parent device */ static int parent_has_perm(struct dev_cgroup *childcg, - struct dev_whitelist_item *wh) + struct dev_exception_item *ex) { struct cgroup *pcg = childcg->css.cgroup->parent; struct dev_cgroup *parent; @@ -338,17 +338,17 @@ static int parent_has_perm(struct dev_cgroup *childcg, if (!pcg) return 1; parent = cgroup_to_devcgroup(pcg); - return may_access_whitelist(parent, wh); + return may_access(parent, ex); } /* - * Modify the whitelist using allow/deny rules. + * Modify the exception list using allow/deny rules. * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD * so we can give a container CAP_MKNOD to let it create devices but not - * modify the whitelist. + * modify the exception list. * It seems likely we'll want to add a CAP_CONTAINER capability to allow * us to also grant CAP_SYS_ADMIN to containers without giving away the - * device whitelist controls, but for now we'll stick with CAP_SYS_ADMIN + * device exception list controls, but for now we'll stick with CAP_SYS_ADMIN * * Taking rules away is always allowed (given CAP_SYS_ADMIN). Granting * new access is only allowed if you're in the top-level cgroup, or your @@ -360,25 +360,25 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, const char *b; char *endp; int count; - struct dev_whitelist_item wh; + struct dev_exception_item ex; if (!capable(CAP_SYS_ADMIN)) return -EPERM; - memset(&wh, 0, sizeof(wh)); + memset(&ex, 0, sizeof(ex)); b = buffer; switch (*b) { case 'a': switch (filetype) { case DEVCG_ALLOW: - if (!parent_has_perm(devcgroup, &wh)) + if (!parent_has_perm(devcgroup, &ex)) return -EPERM; - dev_whitelist_clean(devcgroup); + dev_exception_clean(devcgroup); devcgroup->deny_all = false; break; case DEVCG_DENY: - dev_whitelist_clean(devcgroup); + dev_exception_clean(devcgroup); devcgroup->deny_all = true; break; default: @@ -386,10 +386,10 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, } return 0; case 'b': - wh.type = DEV_BLOCK; + ex.type = DEV_BLOCK; break; case 'c': - wh.type = DEV_CHAR; + ex.type = DEV_CHAR; break; default: return -EINVAL; @@ -399,10 +399,10 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, return -EINVAL; b++; if (*b == '*') { - wh.major = ~0; + ex.major = ~0; b++; } else if (isdigit(*b)) { - wh.major = simple_strtoul(b, &endp, 10); + ex.major = simple_strtoul(b, &endp, 10); b = endp; } else { return -EINVAL; @@ -413,10 +413,10 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, /* read minor */ if (*b == '*') { - wh.minor = ~0; + ex.minor = ~0; b++; } else if (isdigit(*b)) { - wh.minor = simple_strtoul(b, &endp, 10); + ex.minor = simple_strtoul(b, &endp, 10); b = endp; } else { return -EINVAL; @@ -426,13 +426,13 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, for (b++, count = 0; count < 3; count++, b++) { switch (*b) { case 'r': - wh.access |= ACC_READ; + ex.access |= ACC_READ; break; case 'w': - wh.access |= ACC_WRITE; + ex.access |= ACC_WRITE; break; case 'm': - wh.access |= ACC_MKNOD; + ex.access |= ACC_MKNOD; break; case '\n': case '\0': @@ -445,7 +445,7 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, switch (filetype) { case DEVCG_ALLOW: - if (!parent_has_perm(devcgroup, &wh)) + if (!parent_has_perm(devcgroup, &ex)) return -EPERM; /* * If the default policy is to allow by default, try to remove @@ -453,10 +453,10 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, * don't want to break compatibility */ if (devcgroup->deny_all == false) { - dev_whitelist_rm(devcgroup, &wh); + dev_exception_rm(devcgroup, &ex); return 0; } - return dev_whitelist_add(devcgroup, &wh); + return dev_exception_add(devcgroup, &ex); case DEVCG_DENY: /* * If the default policy is to deny by default, try to remove @@ -464,10 +464,10 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, * don't want to break compatibility */ if (devcgroup->deny_all == true) { - dev_whitelist_rm(devcgroup, &wh); + dev_exception_rm(devcgroup, &ex); return 0; } - return dev_whitelist_add(devcgroup, &wh); + return dev_exception_add(devcgroup, &ex); default: return -EINVAL; } @@ -537,17 +537,17 @@ static int __devcgroup_check_permission(struct dev_cgroup *dev_cgroup, short type, u32 major, u32 minor, short access) { - struct dev_whitelist_item wh; + struct dev_exception_item ex; int rc; - memset(&wh, 0, sizeof(wh)); - wh.type = type; - wh.major = major; - wh.minor = minor; - wh.access = access; + memset(&ex, 0, sizeof(ex)); + ex.type = type; + ex.major = major; + ex.minor = minor; + ex.access = access; rcu_read_lock(); - rc = may_access_whitelist(dev_cgroup, &wh); + rc = may_access(dev_cgroup, &ex); rcu_read_unlock(); if (!rc) -- cgit v1.2.3 From cf7f601c067994f371ba77721d1e45fce61a4569 Mon Sep 17 00:00:00 2001 From: David Howells Date: Thu, 13 Sep 2012 13:06:29 +0100 Subject: KEYS: Add payload preparsing opportunity prior to key instantiate or update Give the key type the opportunity to preparse the payload prior to the instantiation and update routines being called. This is done with the provision of two new key type operations: int (*preparse)(struct key_preparsed_payload *prep); void (*free_preparse)(struct key_preparsed_payload *prep); If the first operation is present, then it is called before key creation (in the add/update case) or before the key semaphore is taken (in the update and instantiate cases). The second operation is called to clean up if the first was called. preparse() is given the opportunity to fill in the following structure: struct key_preparsed_payload { char *description; void *type_data[2]; void *payload; const void *data; size_t datalen; size_t quotalen; }; Before the preparser is called, the first three fields will have been cleared, the payload pointer and size will be stored in data and datalen and the default quota size from the key_type struct will be stored into quotalen. The preparser may parse the payload in any way it likes and may store data in the type_data[] and payload fields for use by the instantiate() and update() ops. The preparser may also propose a description for the key by attaching it as a string to the description field. This can be used by passing a NULL or "" description to the add_key() system call or the key_create_or_update() function. This cannot work with request_key() as that required the description to tell the upcall about the key to be created. This, for example permits keys that store PGP public keys to generate their own name from the user ID and public key fingerprint in the key. The instantiate() and update() operations are then modified to look like this: int (*instantiate)(struct key *key, struct key_preparsed_payload *prep); int (*update)(struct key *key, struct key_preparsed_payload *prep); and the new payload data is passed in *prep, whether or not it was preparsed. Signed-off-by: David Howells Signed-off-by: Rusty Russell --- security/keys/encrypted-keys/encrypted.c | 16 +++-- security/keys/key.c | 114 ++++++++++++++++++++++--------- security/keys/keyctl.c | 18 +++-- security/keys/keyring.c | 6 +- security/keys/request_key_auth.c | 8 +-- security/keys/trusted.c | 16 +++-- security/keys/user_defined.c | 14 ++-- 7 files changed, 129 insertions(+), 63 deletions(-) (limited to 'security') diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 2d1bb8af7696..9e1e005c7596 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -773,8 +773,8 @@ static int encrypted_init(struct encrypted_key_payload *epayload, * * On success, return 0. Otherwise return errno. */ -static int encrypted_instantiate(struct key *key, const void *data, - size_t datalen) +static int encrypted_instantiate(struct key *key, + struct key_preparsed_payload *prep) { struct encrypted_key_payload *epayload = NULL; char *datablob = NULL; @@ -782,16 +782,17 @@ static int encrypted_instantiate(struct key *key, const void *data, char *master_desc = NULL; char *decrypted_datalen = NULL; char *hex_encoded_iv = NULL; + size_t datalen = prep->datalen; int ret; - if (datalen <= 0 || datalen > 32767 || !data) + if (datalen <= 0 || datalen > 32767 || !prep->data) return -EINVAL; datablob = kmalloc(datalen + 1, GFP_KERNEL); if (!datablob) return -ENOMEM; datablob[datalen] = 0; - memcpy(datablob, data, datalen); + memcpy(datablob, prep->data, datalen); ret = datablob_parse(datablob, &format, &master_desc, &decrypted_datalen, &hex_encoded_iv); if (ret < 0) @@ -834,16 +835,17 @@ static void encrypted_rcu_free(struct rcu_head *rcu) * * On success, return 0. Otherwise return errno. */ -static int encrypted_update(struct key *key, const void *data, size_t datalen) +static int encrypted_update(struct key *key, struct key_preparsed_payload *prep) { struct encrypted_key_payload *epayload = key->payload.data; struct encrypted_key_payload *new_epayload; char *buf; char *new_master_desc = NULL; const char *format = NULL; + size_t datalen = prep->datalen; int ret = 0; - if (datalen <= 0 || datalen > 32767 || !data) + if (datalen <= 0 || datalen > 32767 || !prep->data) return -EINVAL; buf = kmalloc(datalen + 1, GFP_KERNEL); @@ -851,7 +853,7 @@ static int encrypted_update(struct key *key, const void *data, size_t datalen) return -ENOMEM; buf[datalen] = 0; - memcpy(buf, data, datalen); + memcpy(buf, prep->data, datalen); ret = datablob_parse(buf, &format, &new_master_desc, NULL, NULL); if (ret < 0) goto out; diff --git a/security/keys/key.c b/security/keys/key.c index 50d96d4e06f2..1d039af99f50 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -412,8 +412,7 @@ EXPORT_SYMBOL(key_payload_reserve); * key_construction_mutex. */ static int __key_instantiate_and_link(struct key *key, - const void *data, - size_t datalen, + struct key_preparsed_payload *prep, struct key *keyring, struct key *authkey, unsigned long *_prealloc) @@ -431,7 +430,7 @@ static int __key_instantiate_and_link(struct key *key, /* can't instantiate twice */ if (!test_bit(KEY_FLAG_INSTANTIATED, &key->flags)) { /* instantiate the key */ - ret = key->type->instantiate(key, data, datalen); + ret = key->type->instantiate(key, prep); if (ret == 0) { /* mark the key as being instantiated */ @@ -482,22 +481,37 @@ int key_instantiate_and_link(struct key *key, struct key *keyring, struct key *authkey) { + struct key_preparsed_payload prep; unsigned long prealloc; int ret; + memset(&prep, 0, sizeof(prep)); + prep.data = data; + prep.datalen = datalen; + prep.quotalen = key->type->def_datalen; + if (key->type->preparse) { + ret = key->type->preparse(&prep); + if (ret < 0) + goto error; + } + if (keyring) { ret = __key_link_begin(keyring, key->type, key->description, &prealloc); if (ret < 0) - return ret; + goto error_free_preparse; } - ret = __key_instantiate_and_link(key, data, datalen, keyring, authkey, + ret = __key_instantiate_and_link(key, &prep, keyring, authkey, &prealloc); if (keyring) __key_link_end(keyring, key->type, prealloc); +error_free_preparse: + if (key->type->preparse) + key->type->free_preparse(&prep); +error: return ret; } @@ -706,7 +720,7 @@ void key_type_put(struct key_type *ktype) * if we get an error. */ static inline key_ref_t __key_update(key_ref_t key_ref, - const void *payload, size_t plen) + struct key_preparsed_payload *prep) { struct key *key = key_ref_to_ptr(key_ref); int ret; @@ -722,7 +736,7 @@ static inline key_ref_t __key_update(key_ref_t key_ref, down_write(&key->sem); - ret = key->type->update(key, payload, plen); + ret = key->type->update(key, prep); if (ret == 0) /* updating a negative key instantiates it */ clear_bit(KEY_FLAG_NEGATIVE, &key->flags); @@ -774,6 +788,7 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, unsigned long flags) { unsigned long prealloc; + struct key_preparsed_payload prep; const struct cred *cred = current_cred(); struct key_type *ktype; struct key *keyring, *key = NULL; @@ -789,8 +804,9 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, } key_ref = ERR_PTR(-EINVAL); - if (!ktype->match || !ktype->instantiate) - goto error_2; + if (!ktype->match || !ktype->instantiate || + (!description && !ktype->preparse)) + goto error_put_type; keyring = key_ref_to_ptr(keyring_ref); @@ -798,18 +814,37 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, key_ref = ERR_PTR(-ENOTDIR); if (keyring->type != &key_type_keyring) - goto error_2; + goto error_put_type; + + memset(&prep, 0, sizeof(prep)); + prep.data = payload; + prep.datalen = plen; + prep.quotalen = ktype->def_datalen; + if (ktype->preparse) { + ret = ktype->preparse(&prep); + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_put_type; + } + if (!description) + description = prep.description; + key_ref = ERR_PTR(-EINVAL); + if (!description) + goto error_free_prep; + } ret = __key_link_begin(keyring, ktype, description, &prealloc); - if (ret < 0) - goto error_2; + if (ret < 0) { + key_ref = ERR_PTR(ret); + goto error_free_prep; + } /* if we're going to allocate a new key, we're going to have * to modify the keyring */ ret = key_permission(keyring_ref, KEY_WRITE); if (ret < 0) { key_ref = ERR_PTR(ret); - goto error_3; + goto error_link_end; } /* if it's possible to update this type of key, search for an existing @@ -840,25 +875,27 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, perm, flags); if (IS_ERR(key)) { key_ref = ERR_CAST(key); - goto error_3; + goto error_link_end; } /* instantiate it and link it into the target keyring */ - ret = __key_instantiate_and_link(key, payload, plen, keyring, NULL, - &prealloc); + ret = __key_instantiate_and_link(key, &prep, keyring, NULL, &prealloc); if (ret < 0) { key_put(key); key_ref = ERR_PTR(ret); - goto error_3; + goto error_link_end; } key_ref = make_key_ref(key, is_key_possessed(keyring_ref)); - error_3: +error_link_end: __key_link_end(keyring, ktype, prealloc); - error_2: +error_free_prep: + if (ktype->preparse) + ktype->free_preparse(&prep); +error_put_type: key_type_put(ktype); - error: +error: return key_ref; found_matching_key: @@ -866,10 +903,9 @@ key_ref_t key_create_or_update(key_ref_t keyring_ref, * - we can drop the locks first as we have the key pinned */ __key_link_end(keyring, ktype, prealloc); - key_type_put(ktype); - key_ref = __key_update(key_ref, payload, plen); - goto error; + key_ref = __key_update(key_ref, &prep); + goto error_free_prep; } EXPORT_SYMBOL(key_create_or_update); @@ -888,6 +924,7 @@ EXPORT_SYMBOL(key_create_or_update); */ int key_update(key_ref_t key_ref, const void *payload, size_t plen) { + struct key_preparsed_payload prep; struct key *key = key_ref_to_ptr(key_ref); int ret; @@ -900,18 +937,31 @@ int key_update(key_ref_t key_ref, const void *payload, size_t plen) /* attempt to update it if supported */ ret = -EOPNOTSUPP; - if (key->type->update) { - down_write(&key->sem); - - ret = key->type->update(key, payload, plen); - if (ret == 0) - /* updating a negative key instantiates it */ - clear_bit(KEY_FLAG_NEGATIVE, &key->flags); + if (!key->type->update) + goto error; - up_write(&key->sem); + memset(&prep, 0, sizeof(prep)); + prep.data = payload; + prep.datalen = plen; + prep.quotalen = key->type->def_datalen; + if (key->type->preparse) { + ret = key->type->preparse(&prep); + if (ret < 0) + goto error; } - error: + down_write(&key->sem); + + ret = key->type->update(key, &prep); + if (ret == 0) + /* updating a negative key instantiates it */ + clear_bit(KEY_FLAG_NEGATIVE, &key->flags); + + up_write(&key->sem); + + if (key->type->preparse) + key->type->free_preparse(&prep); +error: return ret; } EXPORT_SYMBOL(key_update); diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 3364fbf46807..505d40be196c 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -46,6 +46,9 @@ static int key_get_type_from_user(char *type, * Extract the description of a new key from userspace and either add it as a * new key to the specified keyring or update a matching key in that keyring. * + * If the description is NULL or an empty string, the key type is asked to + * generate one from the payload. + * * The keyring must be writable so that we can attach the key to it. * * If successful, the new key's serial number is returned, otherwise an error @@ -72,10 +75,17 @@ SYSCALL_DEFINE5(add_key, const char __user *, _type, if (ret < 0) goto error; - description = strndup_user(_description, PAGE_SIZE); - if (IS_ERR(description)) { - ret = PTR_ERR(description); - goto error; + description = NULL; + if (_description) { + description = strndup_user(_description, PAGE_SIZE); + if (IS_ERR(description)) { + ret = PTR_ERR(description); + goto error; + } + if (!*description) { + kfree(description); + description = NULL; + } } /* pull the payload in if one was supplied */ diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 81e7852d281d..f04d8cf81f3c 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -66,7 +66,7 @@ static inline unsigned keyring_hash(const char *desc) * operations. */ static int keyring_instantiate(struct key *keyring, - const void *data, size_t datalen); + struct key_preparsed_payload *prep); static int keyring_match(const struct key *keyring, const void *criterion); static void keyring_revoke(struct key *keyring); static void keyring_destroy(struct key *keyring); @@ -121,12 +121,12 @@ static void keyring_publish_name(struct key *keyring) * Returns 0 on success, -EINVAL if given any data. */ static int keyring_instantiate(struct key *keyring, - const void *data, size_t datalen) + struct key_preparsed_payload *prep) { int ret; ret = -EINVAL; - if (datalen == 0) { + if (prep->datalen == 0) { /* make the keyring available by name if it has one */ keyring_publish_name(keyring); ret = 0; diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 60d4e3f5e4bb..85730d5a5a59 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -19,7 +19,8 @@ #include #include "internal.h" -static int request_key_auth_instantiate(struct key *, const void *, size_t); +static int request_key_auth_instantiate(struct key *, + struct key_preparsed_payload *); static void request_key_auth_describe(const struct key *, struct seq_file *); static void request_key_auth_revoke(struct key *); static void request_key_auth_destroy(struct key *); @@ -42,10 +43,9 @@ struct key_type key_type_request_key_auth = { * Instantiate a request-key authorisation key. */ static int request_key_auth_instantiate(struct key *key, - const void *data, - size_t datalen) + struct key_preparsed_payload *prep) { - key->payload.data = (struct request_key_auth *) data; + key->payload.data = (struct request_key_auth *)prep->data; return 0; } diff --git a/security/keys/trusted.c b/security/keys/trusted.c index 2d5d041f2049..42036c7a0856 100644 --- a/security/keys/trusted.c +++ b/security/keys/trusted.c @@ -927,22 +927,23 @@ static struct trusted_key_payload *trusted_payload_alloc(struct key *key) * * On success, return 0. Otherwise return errno. */ -static int trusted_instantiate(struct key *key, const void *data, - size_t datalen) +static int trusted_instantiate(struct key *key, + struct key_preparsed_payload *prep) { struct trusted_key_payload *payload = NULL; struct trusted_key_options *options = NULL; + size_t datalen = prep->datalen; char *datablob; int ret = 0; int key_cmd; - if (datalen <= 0 || datalen > 32767 || !data) + if (datalen <= 0 || datalen > 32767 || !prep->data) return -EINVAL; datablob = kmalloc(datalen + 1, GFP_KERNEL); if (!datablob) return -ENOMEM; - memcpy(datablob, data, datalen); + memcpy(datablob, prep->data, datalen); datablob[datalen] = '\0'; options = trusted_options_alloc(); @@ -1011,17 +1012,18 @@ static void trusted_rcu_free(struct rcu_head *rcu) /* * trusted_update - reseal an existing key with new PCR values */ -static int trusted_update(struct key *key, const void *data, size_t datalen) +static int trusted_update(struct key *key, struct key_preparsed_payload *prep) { struct trusted_key_payload *p = key->payload.data; struct trusted_key_payload *new_p; struct trusted_key_options *new_o; + size_t datalen = prep->datalen; char *datablob; int ret = 0; if (!p->migratable) return -EPERM; - if (datalen <= 0 || datalen > 32767 || !data) + if (datalen <= 0 || datalen > 32767 || !prep->data) return -EINVAL; datablob = kmalloc(datalen + 1, GFP_KERNEL); @@ -1038,7 +1040,7 @@ static int trusted_update(struct key *key, const void *data, size_t datalen) goto out; } - memcpy(datablob, data, datalen); + memcpy(datablob, prep->data, datalen); datablob[datalen] = '\0'; ret = datablob_parse(datablob, new_p, new_o); if (ret != Opt_update) { diff --git a/security/keys/user_defined.c b/security/keys/user_defined.c index c7660a25a3e4..55dc88939185 100644 --- a/security/keys/user_defined.c +++ b/security/keys/user_defined.c @@ -58,13 +58,14 @@ EXPORT_SYMBOL_GPL(key_type_logon); /* * instantiate a user defined key */ -int user_instantiate(struct key *key, const void *data, size_t datalen) +int user_instantiate(struct key *key, struct key_preparsed_payload *prep) { struct user_key_payload *upayload; + size_t datalen = prep->datalen; int ret; ret = -EINVAL; - if (datalen <= 0 || datalen > 32767 || !data) + if (datalen <= 0 || datalen > 32767 || !prep->data) goto error; ret = key_payload_reserve(key, datalen); @@ -78,7 +79,7 @@ int user_instantiate(struct key *key, const void *data, size_t datalen) /* attach the data */ upayload->datalen = datalen; - memcpy(upayload->data, data, datalen); + memcpy(upayload->data, prep->data, datalen); rcu_assign_keypointer(key, upayload); ret = 0; @@ -92,13 +93,14 @@ EXPORT_SYMBOL_GPL(user_instantiate); * update a user defined key * - the key's semaphore is write-locked */ -int user_update(struct key *key, const void *data, size_t datalen) +int user_update(struct key *key, struct key_preparsed_payload *prep) { struct user_key_payload *upayload, *zap; + size_t datalen = prep->datalen; int ret; ret = -EINVAL; - if (datalen <= 0 || datalen > 32767 || !data) + if (datalen <= 0 || datalen > 32767 || !prep->data) goto error; /* construct a replacement payload */ @@ -108,7 +110,7 @@ int user_update(struct key *key, const void *data, size_t datalen) goto error; upayload->datalen = datalen; - memcpy(upayload->data, data, datalen); + memcpy(upayload->data, prep->data, datalen); /* check the quota and attach the new data */ zap = upayload; -- cgit v1.2.3 From 2dd8ad81e31d0d36a5d448329c646ab43eb17788 Mon Sep 17 00:00:00 2001 From: Konstantin Khlebnikov Date: Mon, 8 Oct 2012 16:28:51 -0700 Subject: mm: use mm->exe_file instead of first VM_EXECUTABLE vma->vm_file Some security modules and oprofile still uses VM_EXECUTABLE for retrieving a task's executable file. After this patch they will use mm->exe_file directly. mm->exe_file is protected with mm->mmap_sem, so locking stays the same. Signed-off-by: Konstantin Khlebnikov Acked-by: Chris Metcalf [arch/tile] Acked-by: Tetsuo Handa [tomoyo] Cc: Alexander Viro Cc: Carsten Otte Cc: Cyrill Gorcunov Cc: Eric Paris Cc: H. Peter Anvin Cc: Hugh Dickins Cc: Ingo Molnar Acked-by: James Morris Cc: Jason Baron Cc: Kentaro Takeda Cc: Matt Helsley Cc: Nick Piggin Cc: Oleg Nesterov Cc: Peter Zijlstra Cc: Robert Richter Cc: Suresh Siddha Cc: Venkatesh Pallipadi Acked-by: Linus Torvalds Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/tomoyo/util.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) (limited to 'security') diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c index 867558c98334..2952ba576fb9 100644 --- a/security/tomoyo/util.c +++ b/security/tomoyo/util.c @@ -949,18 +949,13 @@ bool tomoyo_path_matches_pattern(const struct tomoyo_path_info *filename, const char *tomoyo_get_exe(void) { struct mm_struct *mm = current->mm; - struct vm_area_struct *vma; const char *cp = NULL; if (!mm) return NULL; down_read(&mm->mmap_sem); - for (vma = mm->mmap; vma; vma = vma->vm_next) { - if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) { - cp = tomoyo_realpath_from_path(&vma->vm_file->f_path); - break; - } - } + if (mm->exe_file) + cp = tomoyo_realpath_from_path(&mm->exe_file->f_path); up_read(&mm->mmap_sem); return cp; } -- cgit v1.2.3 From 314e51b9851b4f4e8ab302243ff5a6fc6147f379 Mon Sep 17 00:00:00 2001 From: Konstantin Khlebnikov Date: Mon, 8 Oct 2012 16:29:02 -0700 Subject: mm: kill vma flag VM_RESERVED and mm->reserved_vm counter A long time ago, in v2.4, VM_RESERVED kept swapout process off VMA, currently it lost original meaning but still has some effects: | effect | alternative flags -+------------------------+--------------------------------------------- 1| account as reserved_vm | VM_IO 2| skip in core dump | VM_IO, VM_DONTDUMP 3| do not merge or expand | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP 4| do not mlock | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP This patch removes reserved_vm counter from mm_struct. Seems like nobody cares about it, it does not exported into userspace directly, it only reduces total_vm showed in proc. Thus VM_RESERVED can be replaced with VM_IO or pair VM_DONTEXPAND | VM_DONTDUMP. remap_pfn_range() and io_remap_pfn_range() set VM_IO|VM_DONTEXPAND|VM_DONTDUMP. remap_vmalloc_range() set VM_DONTEXPAND | VM_DONTDUMP. [akpm@linux-foundation.org: drivers/vfio/pci/vfio_pci.c fixup] Signed-off-by: Konstantin Khlebnikov Cc: Alexander Viro Cc: Carsten Otte Cc: Chris Metcalf Cc: Cyrill Gorcunov Cc: Eric Paris Cc: H. Peter Anvin Cc: Hugh Dickins Cc: Ingo Molnar Cc: James Morris Cc: Jason Baron Cc: Kentaro Takeda Cc: Matt Helsley Cc: Nick Piggin Cc: Oleg Nesterov Cc: Peter Zijlstra Cc: Robert Richter Cc: Suresh Siddha Cc: Tetsuo Handa Cc: Venkatesh Pallipadi Acked-by: Linus Torvalds Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/selinux/selinuxfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index 55af8c5b57e6..3a6e8731646c 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -485,7 +485,7 @@ static int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma) return -EACCES; } - vma->vm_flags |= VM_RESERVED; + vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP; vma->vm_ops = &sel_mmap_policy_ops; return 0; -- cgit v1.2.3 From 808d4e3cfdcc52b19276175464f6dbca4df13b09 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Thu, 11 Oct 2012 11:42:01 -0400 Subject: consitify do_mount() arguments Signed-off-by: Al Viro --- security/capability.c | 4 ++-- security/security.c | 4 ++-- security/selinux/hooks.c | 4 ++-- security/smack/smack_lsm.c | 4 ++-- security/tomoyo/common.h | 2 +- security/tomoyo/mount.c | 5 +++-- security/tomoyo/tomoyo.c | 4 ++-- 7 files changed, 14 insertions(+), 13 deletions(-) (limited to 'security') diff --git a/security/capability.c b/security/capability.c index a40aac677c72..b14a30c234b8 100644 --- a/security/capability.c +++ b/security/capability.c @@ -74,8 +74,8 @@ static int cap_sb_statfs(struct dentry *dentry) return 0; } -static int cap_sb_mount(char *dev_name, struct path *path, char *type, - unsigned long flags, void *data) +static int cap_sb_mount(const char *dev_name, struct path *path, + const char *type, unsigned long flags, void *data) { return 0; } diff --git a/security/security.c b/security/security.c index 3724029d0f6d..8dcd4ae10a5f 100644 --- a/security/security.c +++ b/security/security.c @@ -276,8 +276,8 @@ int security_sb_statfs(struct dentry *dentry) return security_ops->sb_statfs(dentry); } -int security_sb_mount(char *dev_name, struct path *path, - char *type, unsigned long flags, void *data) +int security_sb_mount(const char *dev_name, struct path *path, + const char *type, unsigned long flags, void *data) { return security_ops->sb_mount(dev_name, path, type, flags, data); } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 651d8456611a..24ab4148547c 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2452,9 +2452,9 @@ static int selinux_sb_statfs(struct dentry *dentry) return superblock_has_perm(cred, dentry->d_sb, FILESYSTEM__GETATTR, &ad); } -static int selinux_mount(char *dev_name, +static int selinux_mount(const char *dev_name, struct path *path, - char *type, + const char *type, unsigned long flags, void *data) { diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 2874c7316783..38be92ce901e 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -408,8 +408,8 @@ static int smack_sb_statfs(struct dentry *dentry) * Returns 0 if current can write the floor of the filesystem * being mounted on, an error code otherwise. */ -static int smack_sb_mount(char *dev_name, struct path *path, - char *type, unsigned long flags, void *data) +static int smack_sb_mount(const char *dev_name, struct path *path, + const char *type, unsigned long flags, void *data) { struct superblock_smack *sbp = path->dentry->d_sb->s_security; struct smk_audit_info ad; diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index af010b62d544..d4f166bc3508 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -970,7 +970,7 @@ int tomoyo_init_request_info(struct tomoyo_request_info *r, const u8 index); int tomoyo_mkdev_perm(const u8 operation, struct path *path, const unsigned int mode, unsigned int dev); -int tomoyo_mount_permission(char *dev_name, struct path *path, +int tomoyo_mount_permission(const char *dev_name, struct path *path, const char *type, unsigned long flags, void *data_page); int tomoyo_open_control(const u8 type, struct file *file); diff --git a/security/tomoyo/mount.c b/security/tomoyo/mount.c index fe00cdfd0267..390c646013cb 100644 --- a/security/tomoyo/mount.c +++ b/security/tomoyo/mount.c @@ -71,7 +71,8 @@ static bool tomoyo_check_mount_acl(struct tomoyo_request_info *r, * * Caller holds tomoyo_read_lock(). */ -static int tomoyo_mount_acl(struct tomoyo_request_info *r, char *dev_name, +static int tomoyo_mount_acl(struct tomoyo_request_info *r, + const char *dev_name, struct path *dir, const char *type, unsigned long flags) { @@ -183,7 +184,7 @@ static int tomoyo_mount_acl(struct tomoyo_request_info *r, char *dev_name, * * Returns 0 on success, negative value otherwise. */ -int tomoyo_mount_permission(char *dev_name, struct path *path, +int tomoyo_mount_permission(const char *dev_name, struct path *path, const char *type, unsigned long flags, void *data_page) { diff --git a/security/tomoyo/tomoyo.c b/security/tomoyo/tomoyo.c index d88eb3a046ed..a2ee362546ab 100644 --- a/security/tomoyo/tomoyo.c +++ b/security/tomoyo/tomoyo.c @@ -408,8 +408,8 @@ static int tomoyo_path_chroot(struct path *path) * * Returns 0 on success, negative value otherwise. */ -static int tomoyo_sb_mount(char *dev_name, struct path *path, - char *type, unsigned long flags, void *data) +static int tomoyo_sb_mount(const char *dev_name, struct path *path, + const char *type, unsigned long flags, void *data) { return tomoyo_mount_permission(dev_name, path, type, flags, data); } -- cgit v1.2.3 From 45525b26a46cd593cb72070304c4cd7c8391bd37 Mon Sep 17 00:00:00 2001 From: Al Viro Date: Tue, 16 Oct 2012 13:30:07 -0400 Subject: fix a leak in replace_fd() users replace_fd() began with "eats a reference, tries to insert into descriptor table" semantics; at some point I'd switched it to much saner current behaviour ("try to insert into descriptor table, grabbing a new reference if inserted; caller should do fput() in any case"), but forgot to update the callers. Mea culpa... [Spotted by Pavel Roskin, who has really weird system with pipe-fed coredumps as part of what he considers a normal boot ;-)] Signed-off-by: Al Viro --- security/selinux/hooks.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) (limited to 'security') diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 24ab4148547c..61a53367d029 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -2132,18 +2132,14 @@ static inline void flush_unauthorized_files(const struct cred *cred, return; devnull = dentry_open(&selinux_null, O_RDWR, cred); - if (!IS_ERR(devnull)) { - /* replace all the matching ones with this */ - do { - replace_fd(n - 1, get_file(devnull), 0); - } while ((n = iterate_fd(files, n, match_file, cred)) != 0); + if (IS_ERR(devnull)) + devnull = NULL; + /* replace all the matching ones with this */ + do { + replace_fd(n - 1, devnull, 0); + } while ((n = iterate_fd(files, n, match_file, cred)) != 0); + if (devnull) fput(devnull); - } else { - /* just close all the matching ones */ - do { - replace_fd(n - 1, NULL, 0); - } while ((n = iterate_fd(files, n, match_file, cred)) != 0); - } } /* -- cgit v1.2.3 From 43c422eda99b894f18d1cca17bcd2401efaf7bd0 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 17 Oct 2012 13:29:33 -0700 Subject: apparmor: fix apparmor OOPS in audit_log_untrustedstring+0x1c/0x40 The capability defines have moved causing the auto generated names of capabilities that apparmor uses in logging to be incorrect. Fix the autogenerated table source to uapi/linux/capability.h Reported-by: YanHong Reported-by: Krzysztof Kolasa Analyzed-by: Al Viro Signed-off-by: John Johansen Acked-by: David Howells Acked-by: James Morris Signed-off-by: Linus Torvalds --- security/apparmor/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'security') diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index 7b3021cebbea..5706b74c857f 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -57,7 +57,7 @@ cmd_make-rlim = echo "static const char *const rlim_names[RLIM_NLIMITS] = {" \ $(obj)/capability.o : $(obj)/capability_names.h $(obj)/resource.o : $(obj)/rlim_names.h -$(obj)/capability_names.h : $(srctree)/include/linux/capability.h \ +$(obj)/capability_names.h : $(srctree)/include/uapi/linux/capability.h \ $(src)/Makefile $(call cmd,make-caps) $(obj)/rlim_names.h : $(srctree)/include/uapi/asm-generic/resource.h \ -- cgit v1.2.3 From 2e680dd61e80592385338bfbeb86833d1c60546c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 24 Oct 2012 06:27:32 -0700 Subject: apparmor: fix IRQ stack overflow during free_profile BugLink: http://bugs.launchpad.net/bugs/1056078 Profile replacement can cause long chains of profiles to build up when the profile being replaced is pinned. When the pinned profile is finally freed, it puts the reference to its replacement, which may in turn nest another call to free_profile on the stack. Because this may happen for each profile in the replacedby chain this can result in a recusion that causes the stack to overflow. Break this nesting by directly walking the chain of replacedby profiles (ie. use iteration instead of recursion to free the list). This results in at most 2 levels of free_profile being called, while freeing a replacedby chain. Signed-off-by: John Johansen Signed-off-by: James Morris --- security/apparmor/policy.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index cf5fd220309b..813200384d97 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -724,6 +724,8 @@ fail: */ static void free_profile(struct aa_profile *profile) { + struct aa_profile *p; + AA_DEBUG("%s(%p)\n", __func__, profile); if (!profile) @@ -751,7 +753,27 @@ static void free_profile(struct aa_profile *profile) aa_put_dfa(profile->xmatch); aa_put_dfa(profile->policy.dfa); - aa_put_profile(profile->replacedby); + /* put the profile reference for replacedby, but not via + * put_profile(kref_put). + * replacedby can form a long chain that can result in cascading + * frees that blows the stack because kref_put makes a nested fn + * call (it looks like recursion, with free_profile calling + * free_profile) for each profile in the chain lp#1056078. + */ + for (p = profile->replacedby; p; ) { + if (atomic_dec_and_test(&p->base.count.refcount)) { + /* no more refs on p, grab its replacedby */ + struct aa_profile *next = p->replacedby; + /* break the chain */ + p->replacedby = NULL; + /* now free p, chain is broken */ + free_profile(p); + + /* follow up with next profile in the chain */ + p = next; + } else + break; + } kzfree(profile); } -- cgit v1.2.3 From 8c9506d16925f1b1314d93af383ca3134eb534d8 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Thu, 25 Oct 2012 13:37:34 -0700 Subject: cgroup: fix invalid rcu dereference Commit ad676077a2ae ("device_cgroup: convert device_cgroup internally to policy + exceptions") removed rcu locks which are needed in task_devcgroup called in this chain: devcgroup_inode_mknod OR __devcgroup_inode_permission -> __devcgroup_inode_permission -> task_devcgroup -> task_subsys_state -> task_subsys_state_check. Change the code so that task_devcgroup is safely called with rcu read lock held. =============================== [ INFO: suspicious RCU usage. ] 3.6.0-rc5-next-20120913+ #42 Not tainted ------------------------------- include/linux/cgroup.h:553 suspicious rcu_dereference_check() usage! other info that might help us debug this: rcu_scheduler_active = 1, debug_locks = 0 2 locks held by kdevtmpfs/23: #0: (sb_writers){.+.+.+}, at: [] mnt_want_write+0x1f/0x50 #1: (&sb->s_type->i_mutex_key#3/1){+.+.+.}, at: [] kern_path_create+0x7f/0x170 stack backtrace: Pid: 23, comm: kdevtmpfs Not tainted 3.6.0-rc5-next-20120913+ #42 Call Trace: lockdep_rcu_suspicious+0xfd/0x130 devcgroup_inode_mknod+0x19d/0x240 vfs_mknod+0x71/0xf0 handle_create.isra.2+0x72/0x200 devtmpfsd+0x114/0x140 ? handle_create.isra.2+0x200/0x200 kthread+0xd6/0xe0 kernel_thread_helper+0x4/0x10 Signed-off-by: Jiri Slaby Cc: Dave Jones Cc: Tejun Heo Cc: Li Zefan Cc: James Morris Cc: Pavel Emelyanov Acked-by: Serge Hallyn Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/device_cgroup.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 44dfc415a379..46d01fcc0d15 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -533,10 +533,10 @@ struct cgroup_subsys devices_subsys = { * * returns 0 on success, -EPERM case the operation is not permitted */ -static int __devcgroup_check_permission(struct dev_cgroup *dev_cgroup, - short type, u32 major, u32 minor, +static int __devcgroup_check_permission(short type, u32 major, u32 minor, short access) { + struct dev_cgroup *dev_cgroup; struct dev_exception_item ex; int rc; @@ -547,6 +547,7 @@ static int __devcgroup_check_permission(struct dev_cgroup *dev_cgroup, ex.access = access; rcu_read_lock(); + dev_cgroup = task_devcgroup(current); rc = may_access(dev_cgroup, &ex); rcu_read_unlock(); @@ -558,7 +559,6 @@ static int __devcgroup_check_permission(struct dev_cgroup *dev_cgroup, int __devcgroup_inode_permission(struct inode *inode, int mask) { - struct dev_cgroup *dev_cgroup = task_devcgroup(current); short type, access = 0; if (S_ISBLK(inode->i_mode)) @@ -570,13 +570,12 @@ int __devcgroup_inode_permission(struct inode *inode, int mask) if (mask & MAY_READ) access |= ACC_READ; - return __devcgroup_check_permission(dev_cgroup, type, imajor(inode), - iminor(inode), access); + return __devcgroup_check_permission(type, imajor(inode), iminor(inode), + access); } int devcgroup_inode_mknod(int mode, dev_t dev) { - struct dev_cgroup *dev_cgroup = task_devcgroup(current); short type; if (!S_ISBLK(mode) && !S_ISCHR(mode)) @@ -587,7 +586,7 @@ int devcgroup_inode_mknod(int mode, dev_t dev) else type = DEV_CHAR; - return __devcgroup_check_permission(dev_cgroup, type, MAJOR(dev), - MINOR(dev), ACC_MKNOD); + return __devcgroup_check_permission(type, MAJOR(dev), MINOR(dev), + ACC_MKNOD); } -- cgit v1.2.3 From 5b7aa7d5bb2c5cf7fc05aaa41561af321706ab5f Mon Sep 17 00:00:00 2001 From: Aristeu Rozanski Date: Thu, 25 Oct 2012 13:37:38 -0700 Subject: device_cgroup: rename deny_all to behavior This was done in a v2 patch but v1 ended up being committed. The variable name is less confusing and stores the default behavior when no matching exception exists. Signed-off-by: Aristeu Rozanski Cc: Dave Jones Cc: Tejun Heo Cc: Li Zefan Cc: James Morris Cc: Pavel Emelyanov Acked-by: Serge Hallyn Cc: Jiri Slaby Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/device_cgroup.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 46d01fcc0d15..76503df23770 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -42,7 +42,10 @@ struct dev_exception_item { struct dev_cgroup { struct cgroup_subsys_state css; struct list_head exceptions; - bool deny_all; + enum { + DEVCG_DEFAULT_ALLOW, + DEVCG_DEFAULT_DENY, + } behavior; }; static inline struct dev_cgroup *css_to_devcgroup(struct cgroup_subsys_state *s) @@ -182,13 +185,13 @@ static struct cgroup_subsys_state *devcgroup_create(struct cgroup *cgroup) parent_cgroup = cgroup->parent; if (parent_cgroup == NULL) - dev_cgroup->deny_all = false; + dev_cgroup->behavior = DEVCG_DEFAULT_ALLOW; else { parent_dev_cgroup = cgroup_to_devcgroup(parent_cgroup); mutex_lock(&devcgroup_mutex); ret = dev_exceptions_copy(&dev_cgroup->exceptions, &parent_dev_cgroup->exceptions); - dev_cgroup->deny_all = parent_dev_cgroup->deny_all; + dev_cgroup->behavior = parent_dev_cgroup->behavior; mutex_unlock(&devcgroup_mutex); if (ret) { kfree(dev_cgroup); @@ -260,7 +263,7 @@ static int devcgroup_seq_read(struct cgroup *cgroup, struct cftype *cft, * - List the exceptions in case the default policy is to deny * This way, the file remains as a "whitelist of devices" */ - if (devcgroup->deny_all == false) { + if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { set_access(acc, ACC_MASK); set_majmin(maj, ~0); set_majmin(min, ~0); @@ -314,12 +317,12 @@ static int may_access(struct dev_cgroup *dev_cgroup, * In two cases we'll consider this new exception valid: * - the dev cgroup has its default policy to allow + exception list: * the new exception should *not* match any of the exceptions - * (!deny_all, !match) + * (behavior == DEVCG_DEFAULT_ALLOW, !match) * - the dev cgroup has its default policy to deny + exception list: * the new exception *should* match the exceptions - * (deny_all, match) + * (behavior == DEVCG_DEFAULT_DENY, match) */ - if (dev_cgroup->deny_all == match) + if ((dev_cgroup->behavior == DEVCG_DEFAULT_DENY) == match) return 1; return 0; } @@ -375,11 +378,11 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, if (!parent_has_perm(devcgroup, &ex)) return -EPERM; dev_exception_clean(devcgroup); - devcgroup->deny_all = false; + devcgroup->behavior = DEVCG_DEFAULT_ALLOW; break; case DEVCG_DENY: dev_exception_clean(devcgroup); - devcgroup->deny_all = true; + devcgroup->behavior = DEVCG_DEFAULT_DENY; break; default: return -EINVAL; @@ -452,7 +455,7 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, * an matching exception instead. And be silent about it: we * don't want to break compatibility */ - if (devcgroup->deny_all == false) { + if (devcgroup->behavior == DEVCG_DEFAULT_ALLOW) { dev_exception_rm(devcgroup, &ex); return 0; } @@ -463,7 +466,7 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, * an matching exception instead. And be silent about it: we * don't want to break compatibility */ - if (devcgroup->deny_all == true) { + if (devcgroup->behavior == DEVCG_DEFAULT_DENY) { dev_exception_rm(devcgroup, &ex); return 0; } -- cgit v1.2.3 From 26fd8405dd470cb8b54cb96859b7dd437e5e1391 Mon Sep 17 00:00:00 2001 From: Aristeu Rozanski Date: Thu, 25 Oct 2012 13:37:41 -0700 Subject: device_cgroup: stop using simple_strtoul() Convert the code to use kstrtou32() instead of simple_strtoul() which is deprecated. The real size of the variables are u32, so use kstrtou32 instead of kstrtoul Signed-off-by: Aristeu Rozanski Cc: Dave Jones Cc: Tejun Heo Cc: Li Zefan Cc: James Morris Cc: Pavel Emelyanov Acked-by: Serge Hallyn Cc: Jiri Slaby Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/device_cgroup.c | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 76503df23770..4fbae8d0b36c 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -361,8 +361,8 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, int filetype, const char *buffer) { const char *b; - char *endp; - int count; + char temp[12]; /* 11 + 1 characters needed for a u32 */ + int count, rc; struct dev_exception_item ex; if (!capable(CAP_SYS_ADMIN)) @@ -405,8 +405,16 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, ex.major = ~0; b++; } else if (isdigit(*b)) { - ex.major = simple_strtoul(b, &endp, 10); - b = endp; + memset(temp, 0, sizeof(temp)); + for (count = 0; count < sizeof(temp) - 1; count++) { + temp[count] = *b; + b++; + if (!isdigit(*b)) + break; + } + rc = kstrtou32(temp, 10, &ex.major); + if (rc) + return -EINVAL; } else { return -EINVAL; } @@ -419,8 +427,16 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, ex.minor = ~0; b++; } else if (isdigit(*b)) { - ex.minor = simple_strtoul(b, &endp, 10); - b = endp; + memset(temp, 0, sizeof(temp)); + for (count = 0; count < sizeof(temp) - 1; count++) { + temp[count] = *b; + b++; + if (!isdigit(*b)) + break; + } + rc = kstrtou32(temp, 10, &ex.minor); + if (rc) + return -EINVAL; } else { return -EINVAL; } -- cgit v1.2.3 From 4cef7299b4786879a3e113e84084a72b24590c5b Mon Sep 17 00:00:00 2001 From: Aristeu Rozanski Date: Thu, 25 Oct 2012 13:37:45 -0700 Subject: device_cgroup: add proper checking when changing default behavior Before changing a group's default behavior to ALLOW, we must check if its parent's behavior is also ALLOW. Signed-off-by: Aristeu Rozanski Cc: Tejun Heo Cc: Li Zefan Cc: James Morris Cc: Pavel Emelyanov Acked-by: Serge Hallyn Cc: Jiri Slaby Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- security/device_cgroup.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) (limited to 'security') diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 4fbae8d0b36c..842c254396db 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -344,6 +344,17 @@ static int parent_has_perm(struct dev_cgroup *childcg, return may_access(parent, ex); } +/** + * may_allow_all - checks if it's possible to change the behavior to + * allow based on parent's rules. + * @parent: device cgroup's parent + * returns: != 0 in case it's allowed, 0 otherwise + */ +static inline int may_allow_all(struct dev_cgroup *parent) +{ + return parent->behavior == DEVCG_DEFAULT_ALLOW; +} + /* * Modify the exception list using allow/deny rules. * CAP_SYS_ADMIN is needed for this. It's at least separate from CAP_MKNOD @@ -364,6 +375,8 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, char temp[12]; /* 11 + 1 characters needed for a u32 */ int count, rc; struct dev_exception_item ex; + struct cgroup *p = devcgroup->css.cgroup; + struct dev_cgroup *parent = cgroup_to_devcgroup(p->parent); if (!capable(CAP_SYS_ADMIN)) return -EPERM; @@ -375,9 +388,13 @@ static int devcgroup_update_access(struct dev_cgroup *devcgroup, case 'a': switch (filetype) { case DEVCG_ALLOW: - if (!parent_has_perm(devcgroup, &ex)) + if (!may_allow_all(parent)) return -EPERM; dev_exception_clean(devcgroup); + rc = dev_exceptions_copy(&devcgroup->exceptions, + &parent->exceptions); + if (rc) + return rc; devcgroup->behavior = DEVCG_DEFAULT_ALLOW; break; case DEVCG_DENY: -- cgit v1.2.3