diff options
Diffstat (limited to 'security')
-rw-r--r-- | security/Kconfig | 5 | ||||
-rw-r--r-- | security/Makefile | 4 | ||||
-rw-r--r-- | security/inode.c | 7 | ||||
-rw-r--r-- | security/integrity/ima/Kconfig | 55 | ||||
-rw-r--r-- | security/integrity/ima/Makefile | 9 | ||||
-rw-r--r-- | security/integrity/ima/ima.h | 166 | ||||
-rw-r--r-- | security/integrity/ima/ima_api.c | 190 | ||||
-rw-r--r-- | security/integrity/ima/ima_audit.c | 78 | ||||
-rw-r--r-- | security/integrity/ima/ima_crypto.c | 140 | ||||
-rw-r--r-- | security/integrity/ima/ima_fs.c | 376 | ||||
-rw-r--r-- | security/integrity/ima/ima_iint.c | 202 | ||||
-rw-r--r-- | security/integrity/ima/ima_init.c | 96 | ||||
-rw-r--r-- | security/integrity/ima/ima_main.c | 327 | ||||
-rw-r--r-- | security/integrity/ima/ima_policy.c | 413 | ||||
-rw-r--r-- | security/integrity/ima/ima_queue.c | 140 | ||||
-rw-r--r-- | security/selinux/hooks.c | 241 | ||||
-rw-r--r-- | security/selinux/include/objsec.h | 2 | ||||
-rw-r--r-- | security/selinux/include/security.h | 8 |
18 files changed, 2277 insertions, 182 deletions
diff --git a/security/Kconfig b/security/Kconfig index 9438535d7fd0..bf129f87de7e 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -55,7 +55,8 @@ config SECURITYFS bool "Enable the securityfs filesystem" help This will build the securityfs filesystem. It is currently used by - the TPM bios character driver. It is not used by SELinux or SMACK. + the TPM bios character driver and IMA, an integrity provider. It is + not used by SELinux or SMACK. If you are unsure how to answer this question, answer N. @@ -135,5 +136,7 @@ config SECURITY_DEFAULT_MMAP_MIN_ADDR source security/selinux/Kconfig source security/smack/Kconfig +source security/integrity/ima/Kconfig + endmenu diff --git a/security/Makefile b/security/Makefile index c05c127fff9a..595536cbffb2 100644 --- a/security/Makefile +++ b/security/Makefile @@ -17,3 +17,7 @@ obj-$(CONFIG_SECURITY_SELINUX) += selinux/built-in.o obj-$(CONFIG_SECURITY_SMACK) += smack/built-in.o obj-$(CONFIG_SECURITY_ROOTPLUG) += root_plug.o obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o + +# Object integrity file lists +subdir-$(CONFIG_IMA) += integrity/ima +obj-$(CONFIG_IMA) += integrity/ima/built-in.o diff --git a/security/inode.c b/security/inode.c index 007ef252dde7..f3b91bfbe4cb 100644 --- a/security/inode.c +++ b/security/inode.c @@ -202,12 +202,11 @@ static int create_by_name(const char *name, mode_t mode, * This function returns a pointer to a dentry if it succeeds. This * pointer must be passed to the securityfs_remove() function when the file is * to be removed (no automatic cleanup happens if your module is unloaded, - * you are responsible here). If an error occurs, %NULL is returned. + * you are responsible here). If an error occurs, the function will return + * the erorr value (via ERR_PTR). * * If securityfs is not enabled in the kernel, the value %-ENODEV is - * returned. It is not wise to check for this value, but rather, check for - * %NULL or !%NULL instead as to eliminate the need for #ifdef in the calling - * code. + * returned. */ struct dentry *securityfs_create_file(const char *name, mode_t mode, struct dentry *parent, void *data, diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig new file mode 100644 index 000000000000..3d2b6ee778a0 --- /dev/null +++ b/security/integrity/ima/Kconfig @@ -0,0 +1,55 @@ +# IBM Integrity Measurement Architecture +# +config IMA + bool "Integrity Measurement Architecture(IMA)" + depends on ACPI + select SECURITYFS + select CRYPTO + select CRYPTO_HMAC + select CRYPTO_MD5 + select CRYPTO_SHA1 + select TCG_TPM + select TCG_TIS + help + The Trusted Computing Group(TCG) runtime Integrity + Measurement Architecture(IMA) maintains a list of hash + values of executables and other sensitive system files, + as they are read or executed. If an attacker manages + to change the contents of an important system file + being measured, we can tell. + + If your system has a TPM chip, then IMA also maintains + an aggregate integrity value over this list inside the + TPM hardware, so that the TPM can prove to a third party + whether or not critical system files have been modified. + Read <http://www.usenix.org/events/sec04/tech/sailer.html> + to learn more about IMA. + If unsure, say N. + +config IMA_MEASURE_PCR_IDX + int + depends on IMA + range 8 14 + default 10 + help + IMA_MEASURE_PCR_IDX determines the TPM PCR register index + that IMA uses to maintain the integrity aggregate of the + measurement list. If unsure, use the default 10. + +config IMA_AUDIT + bool + depends on IMA + default y + help + This option adds a kernel parameter 'ima_audit', which + allows informational auditing messages to be enabled + at boot. If this option is selected, informational integrity + auditing messages can be enabled with 'ima_audit=1' on + the kernel command line. + +config IMA_LSM_RULES + bool + depends on IMA && (SECURITY_SELINUX || SECURITY_SMACK) + default y + help + Disabling this option will disregard LSM based policy rules diff --git a/security/integrity/ima/Makefile b/security/integrity/ima/Makefile new file mode 100644 index 000000000000..787c4cb916cd --- /dev/null +++ b/security/integrity/ima/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for building Trusted Computing Group's(TCG) runtime Integrity +# Measurement Architecture(IMA). +# + +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_iint.o ima_audit.o diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h new file mode 100644 index 000000000000..e3c16a21a38e --- /dev/null +++ b/security/integrity/ima/ima.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer <sailer@watson.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima.h + * internal Integrity Measurement Architecture (IMA) definitions + */ + +#ifndef __LINUX_IMA_H +#define __LINUX_IMA_H + +#include <linux/types.h> +#include <linux/crypto.h> +#include <linux/security.h> +#include <linux/hash.h> +#include <linux/tpm.h> +#include <linux/audit.h> + +enum ima_show_type { IMA_SHOW_BINARY, IMA_SHOW_ASCII }; +enum tpm_pcrs { TPM_PCR0 = 0, TPM_PCR8 = 8 }; + +/* digest size for IMA, fits SHA1 or MD5 */ +#define IMA_DIGEST_SIZE 20 +#define IMA_EVENT_NAME_LEN_MAX 255 + +#define IMA_HASH_BITS 9 +#define IMA_MEASURE_HTABLE_SIZE (1 << IMA_HASH_BITS) + +/* set during initialization */ +extern int ima_initialized; +extern int ima_used_chip; +extern char *ima_hash; + +/* IMA inode template definition */ +struct ima_template_data { + u8 digest[IMA_DIGEST_SIZE]; /* sha1/md5 measurement hash */ + char file_name[IMA_EVENT_NAME_LEN_MAX + 1]; /* name + \0 */ +}; + +struct ima_template_entry { + u8 digest[IMA_DIGEST_SIZE]; /* sha1 or md5 measurement hash */ + char *template_name; + int template_len; + struct ima_template_data template; +}; + +struct ima_queue_entry { + struct hlist_node hnext; /* place in hash collision list */ + struct list_head later; /* place in ima_measurements list */ + struct ima_template_entry *entry; +}; +extern struct list_head ima_measurements; /* list of all measurements */ + +/* declarations */ +void integrity_audit_msg(int audit_msgno, struct inode *inode, + const unsigned char *fname, const char *op, + const char *cause, int result, int info); + +/* Internal IMA function definitions */ +void ima_iintcache_init(void); +int ima_init(void); +void ima_cleanup(void); +int ima_fs_init(void); +void ima_fs_cleanup(void); +int ima_add_template_entry(struct ima_template_entry *entry, int violation, + const char *op, struct inode *inode); +int ima_calc_hash(struct file *file, char *digest); +int ima_calc_template_hash(int template_len, void *template, char *digest); +int ima_calc_boot_aggregate(char *digest); +void ima_add_violation(struct inode *inode, const unsigned char *filename, + const char *op, const char *cause); + +/* + * used to protect h_table and sha_table + */ +extern spinlock_t ima_queue_lock; + +struct ima_h_table { + atomic_long_t len; /* number of stored measurements in the list */ + atomic_long_t violations; + struct hlist_head queue[IMA_MEASURE_HTABLE_SIZE]; +}; +extern struct ima_h_table ima_htable; + +static inline unsigned long ima_hash_key(u8 *digest) +{ + return hash_long(*digest, IMA_HASH_BITS); +} + +/* iint cache flags */ +#define IMA_MEASURED 1 +#define IMA_IINT_DUMP_STACK 512 + +/* integrity data associated with an inode */ +struct ima_iint_cache { + u64 version; /* track inode changes */ + unsigned long flags; + u8 digest[IMA_DIGEST_SIZE]; + struct mutex mutex; /* protects: version, flags, digest */ + long readcount; /* measured files readcount */ + long writecount; /* measured files writecount */ + long opencount; /* opens reference count */ + struct kref refcount; /* ima_iint_cache reference count */ + struct rcu_head rcu; +}; + +/* LIM API function definitions */ +int ima_must_measure(struct ima_iint_cache *iint, struct inode *inode, + int mask, int function); +int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file); +void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, + 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); + +/* radix tree calls to lookup, insert, delete + * integrity data associated with an inode. + */ +struct ima_iint_cache *ima_iint_insert(struct inode *inode); +struct ima_iint_cache *ima_iint_find_get(struct inode *inode); +struct ima_iint_cache *ima_iint_find_insert_get(struct inode *inode); +void ima_iint_delete(struct inode *inode); +void iint_free(struct kref *kref); +void iint_rcu_free(struct rcu_head *rcu); + +/* IMA policy related functions */ +enum ima_hooks { PATH_CHECK = 1, FILE_MMAP, BPRM_CHECK }; + +int ima_match_policy(struct inode *inode, enum ima_hooks func, int mask); +void ima_init_policy(void); +void ima_update_policy(void); +int ima_parse_add_rule(char *); +void ima_delete_rules(void); + +/* LSM based policy rules require audit */ +#ifdef CONFIG_IMA_LSM_RULES + +#define security_filter_rule_init security_audit_rule_init +#define security_filter_rule_match security_audit_rule_match + +#else + +static inline int security_filter_rule_init(u32 field, u32 op, char *rulestr, + void **lsmrule) +{ + return -EINVAL; +} + +static inline int security_filter_rule_match(u32 secid, u32 field, u32 op, + void *lsmrule, + struct audit_context *actx) +{ + return -EINVAL; +} +#endif /* CONFIG_IMA_LSM_RULES */ +#endif diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c new file mode 100644 index 000000000000..a148a25804f6 --- /dev/null +++ b/security/integrity/ima/ima_api.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2008 IBM Corporation + * + * Author: Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_api.c + * Implements must_measure, collect_measurement, store_measurement, + * and store_template. + */ +#include <linux/module.h> + +#include "ima.h" +static char *IMA_TEMPLATE_NAME = "ima"; + +/* + * ima_store_template - store ima template measurements + * + * Calculate the hash of a template entry, add the template entry + * to an ordered list of measurement entries maintained inside the kernel, + * and also update the aggregate integrity value (maintained inside the + * configured TPM PCR) over the hashes of the current list of measurement + * entries. + * + * Applications retrieve the current kernel-held measurement list through + * the securityfs entries in /sys/kernel/security/ima. The signed aggregate + * TPM PCR (called quote) can be retrieved using a TPM user space library + * and is used to validate the measurement list. + * + * Returns 0 on success, error code otherwise + */ +int ima_store_template(struct ima_template_entry *entry, + int violation, struct inode *inode) +{ + const char *op = "add_template_measure"; + const char *audit_cause = "hashing_error"; + int result; + + memset(entry->digest, 0, sizeof(entry->digest)); + entry->template_name = IMA_TEMPLATE_NAME; + entry->template_len = sizeof(entry->template); + + if (!violation) { + result = ima_calc_template_hash(entry->template_len, + &entry->template, + entry->digest); + if (result < 0) { + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, + entry->template_name, op, + audit_cause, result, 0); + return result; + } + } + result = ima_add_template_entry(entry, violation, op, inode); + return result; +} + +/* + * ima_add_violation - add violation to measurement list. + * + * Violations are flagged in the measurement list with zero hash values. + * By extending the PCR with 0xFF's instead of with zeroes, the PCR + * value is invalidated. + */ +void ima_add_violation(struct inode *inode, const unsigned char *filename, + const char *op, const char *cause) +{ + struct ima_template_entry *entry; + int violation = 1; + int result; + + /* can overflow, only indicator */ + atomic_long_inc(&ima_htable.violations); + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + result = -ENOMEM; + goto err_out; + } + memset(&entry->template, 0, sizeof(entry->template)); + strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); + result = ima_store_template(entry, violation, inode); + if (result < 0) + kfree(entry); +err_out: + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, + op, cause, result, 0); +} + +/** + * ima_must_measure - 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 (PATH_CHECK, BPRM_CHECK, FILE_MMAP) + * + * The policy is defined in terms of keypairs: + * subj=, obj=, type=, func=, mask=, fsmagic= + * subj,obj, and type: are LSM specific. + * func: PATH_CHECK | BPRM_CHECK | FILE_MMAP + * mask: contains the permission mask + * fsmagic: hex value + * + * Must be called with iint->mutex held. + * + * Return 0 to measure. Return 1 if already measured. + * For matching a DONT_MEASURE policy, no policy, or other + * error, return an error code. +*/ +int ima_must_measure(struct ima_iint_cache *iint, struct inode *inode, + int mask, int function) +{ + int must_measure; + + if (iint->flags & IMA_MEASURED) + return 1; + + must_measure = ima_match_policy(inode, function, mask); + return must_measure ? 0 : -EACCES; +} + +/* + * ima_collect_measurement - collect file measurement + * + * Calculate the file hash, if it doesn't already exist, + * storing the measurement and i_version in the iint. + * + * Must be called with iint->mutex held. + * + * Return 0 on success, error code otherwise + */ +int ima_collect_measurement(struct ima_iint_cache *iint, struct file *file) +{ + int result = -EEXIST; + + if (!(iint->flags & IMA_MEASURED)) { + 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) + iint->version = i_version; + } + return result; +} + +/* + * ima_store_measurement - store file measurement + * + * Create an "ima" template and then store the template by calling + * ima_store_template. + * + * We only get here if the inode has not already been measured, + * but the measurement could already exist: + * - multiple copies of the same file on either the same or + * different filesystems. + * - the inode was previously flushed as well as the iint info, + * containing the hashing info. + * + * Must be called with iint->mutex held. + */ +void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, + const unsigned char *filename) +{ + const char *op = "add_template_measure"; + const char *audit_cause = "ENOMEM"; + int result = -ENOMEM; + struct inode *inode = file->f_dentry->d_inode; + struct ima_template_entry *entry; + int violation = 0; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, + op, audit_cause, result, 0); + return; + } + memset(&entry->template, 0, sizeof(entry->template)); + memcpy(entry->template.digest, iint->digest, IMA_DIGEST_SIZE); + strncpy(entry->template.file_name, filename, IMA_EVENT_NAME_LEN_MAX); + + result = ima_store_template(entry, violation, inode); + if (!result) + iint->flags |= IMA_MEASURED; + else + kfree(entry); +} diff --git a/security/integrity/ima/ima_audit.c b/security/integrity/ima/ima_audit.c new file mode 100644 index 000000000000..8a0f1e23ccf1 --- /dev/null +++ b/security/integrity/ima/ima_audit.c @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2008 IBM Corporation + * Author: Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: integrity_audit.c + * Audit calls for the integrity subsystem + */ + +#include <linux/fs.h> +#include <linux/audit.h> +#include "ima.h" + +static int ima_audit; + +#ifdef CONFIG_IMA_AUDIT + +/* ima_audit_setup - enable informational auditing messages */ +static int __init ima_audit_setup(char *str) +{ + unsigned long audit; + int rc; + char *op; + + rc = strict_strtoul(str, 0, &audit); + if (rc || audit > 1) + printk(KERN_INFO "ima: invalid ima_audit value\n"); + else + ima_audit = audit; + op = ima_audit ? "ima_audit_enabled" : "ima_audit_not_enabled"; + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, NULL, op, 0, 0); + return 1; +} +__setup("ima_audit=", ima_audit_setup); +#endif + +void integrity_audit_msg(int audit_msgno, struct inode *inode, + const unsigned char *fname, const char *op, + const char *cause, int result, int audit_info) +{ + struct audit_buffer *ab; + + if (!ima_audit && audit_info == 1) /* Skip informational messages */ + return; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, audit_msgno); + audit_log_format(ab, "integrity: pid=%d uid=%u auid=%u", + current->pid, current->cred->uid, + audit_get_loginuid(current)); + audit_log_task_context(ab); + switch (audit_msgno) { + case AUDIT_INTEGRITY_DATA: + case AUDIT_INTEGRITY_METADATA: + case AUDIT_INTEGRITY_PCR: + audit_log_format(ab, " op=%s cause=%s", op, cause); + break; + case AUDIT_INTEGRITY_HASH: + audit_log_format(ab, " op=%s hash=%s", op, cause); + break; + case AUDIT_INTEGRITY_STATUS: + default: + audit_log_format(ab, " op=%s", op); + } + audit_log_format(ab, " comm="); + audit_log_untrustedstring(ab, current->comm); + if (fname) { + audit_log_format(ab, " name="); + audit_log_untrustedstring(ab, fname); + } + if (inode) + audit_log_format(ab, " dev=%s ino=%lu", + inode->i_sb->s_id, inode->i_ino); + audit_log_format(ab, " res=%d", result); + audit_log_end(ab); +} diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c new file mode 100644 index 000000000000..c2a46e40999d --- /dev/null +++ b/security/integrity/ima/ima_crypto.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * Kylene Hall <kjhall@us.ibm.com> + * + * 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. + * + * File: ima_crypto.c + * Calculates md5/sha1 file hash, template hash, boot-aggreate hash + */ + +#include <linux/kernel.h> +#include <linux/file.h> +#include <linux/crypto.h> +#include <linux/scatterlist.h> +#include <linux/err.h> +#include "ima.h" + +static int init_desc(struct hash_desc *desc) +{ + int rc; + + desc->tfm = crypto_alloc_hash(ima_hash, 0, CRYPTO_ALG_ASYNC); + if (IS_ERR(desc->tfm)) { + pr_info("failed to load %s transform: %ld\n", + ima_hash, PTR_ERR(desc->tfm)); + rc = PTR_ERR(desc->tfm); + return rc; + } + desc->flags = 0; + rc = crypto_hash_init(desc); + if (rc) + crypto_free_hash(desc->tfm); + return rc; +} + +/* + * Calculate the MD5/SHA1 file digest + */ +int ima_calc_hash(struct file *file, char *digest) +{ + struct hash_desc desc; + struct scatterlist sg[1]; + loff_t i_size; + char *rbuf; + int rc, offset = 0; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + rbuf = kzalloc(PAGE_SIZE, GFP_KERNEL); + if (!rbuf) { + rc = -ENOMEM; + goto out; + } + i_size = i_size_read(file->f_dentry->d_inode); + while (offset < i_size) { + int rbuf_len; + + rbuf_len = kernel_read(file, offset, rbuf, PAGE_SIZE); + if (rbuf_len < 0) { + rc = rbuf_len; + break; + } + offset += rbuf_len; + sg_set_buf(sg, rbuf, rbuf_len); + + rc = crypto_hash_update(&desc, sg, rbuf_len); + if (rc) + break; + } + kfree(rbuf); + if (!rc) + rc = crypto_hash_final(&desc, digest); +out: + crypto_free_hash(desc.tfm); + return rc; +} + +/* + * Calculate the hash of a given template + */ +int ima_calc_template_hash(int template_len, void *template, char *digest) +{ + struct hash_desc desc; + struct scatterlist sg[1]; + int rc; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + sg_set_buf(sg, template, template_len); + rc = crypto_hash_update(&desc, sg, template_len); + if (!rc) + rc = crypto_hash_final(&desc, digest); + crypto_free_hash(desc.tfm); + return rc; +} + +static void ima_pcrread(int idx, u8 *pcr) +{ + if (!ima_used_chip) + return; + + if (tpm_pcr_read(TPM_ANY_NUM, idx, pcr) != 0) + pr_err("Error Communicating to TPM chip\n"); +} + +/* + * Calculate the boot aggregate hash + */ +int ima_calc_boot_aggregate(char *digest) +{ + struct hash_desc desc; + struct scatterlist sg; + u8 pcr_i[IMA_DIGEST_SIZE]; + int rc, i; + + rc = init_desc(&desc); + if (rc != 0) + return rc; + + /* cumulative sha1 over tpm registers 0-7 */ + for (i = TPM_PCR0; i < TPM_PCR8; i++) { + ima_pcrread(i, pcr_i); + /* now accumulate with current aggregate */ + sg_init_one(&sg, pcr_i, IMA_DIGEST_SIZE); + rc = crypto_hash_update(&desc, &sg, IMA_DIGEST_SIZE); + } + if (!rc) + crypto_hash_final(&desc, digest); + crypto_free_hash(desc.tfm); + return rc; +} diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c new file mode 100644 index 000000000000..573780c76f1f --- /dev/null +++ b/security/integrity/ima/ima_fs.c @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Kylene Hall <kjhall@us.ibm.com> + * Reiner Sailer <sailer@us.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_fs.c + * implemenents security file system for reporting + * current measurement list and IMA statistics + */ +#include <linux/module.h> +#include <linux/seq_file.h> +#include <linux/rculist.h> +#include <linux/rcupdate.h> +#include <linux/parser.h> + +#include "ima.h" + +static int valid_policy = 1; +#define TMPBUFLEN 12 +static ssize_t ima_show_htable_value(char __user *buf, size_t count, + loff_t *ppos, atomic_long_t *val) +{ + char tmpbuf[TMPBUFLEN]; + ssize_t len; + + len = scnprintf(tmpbuf, TMPBUFLEN, "%li\n", atomic_long_read(val)); + return simple_read_from_buffer(buf, count, ppos, tmpbuf, len); +} + +static ssize_t ima_show_htable_violations(struct file *filp, + char __user *buf, + size_t count, loff_t *ppos) +{ + return ima_show_htable_value(buf, count, ppos, &ima_htable.violations); +} + +static struct file_operations ima_htable_violations_ops = { + .read = ima_show_htable_violations +}; + +static ssize_t ima_show_measurements_count(struct file *filp, + char __user *buf, + size_t count, loff_t *ppos) +{ + return ima_show_htable_value(buf, count, ppos, &ima_htable.len); + +} + +static struct file_operations ima_measurements_count_ops = { + .read = ima_show_measurements_count +}; + +/* returns pointer to hlist_node */ +static void *ima_measurements_start(struct seq_file *m, loff_t *pos) +{ + loff_t l = *pos; + struct ima_queue_entry *qe; + + /* we need a lock since pos could point beyond last element */ + rcu_read_lock(); + list_for_each_entry_rcu(qe, &ima_measurements, later) { + if (!l--) { + rcu_read_unlock(); + return qe; + } + } + rcu_read_unlock(); + return NULL; +} + +static void *ima_measurements_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct ima_queue_entry *qe = v; + + /* lock protects when reading beyond last element + * against concurrent list-extension + */ + rcu_read_lock(); + qe = list_entry(rcu_dereference(qe->later.next), + struct ima_queue_entry, later); + rcu_read_unlock(); + (*pos)++; + + return (&qe->later == &ima_measurements) ? NULL : qe; +} + +static void ima_measurements_stop(struct seq_file *m, void *v) +{ +} + +static void ima_putc(struct seq_file *m, void *data, int datalen) +{ + while (datalen--) + seq_putc(m, *(char *)data++); +} + +/* print format: + * 32bit-le=pcr# + * char[20]=template digest + * 32bit-le=template name size + * char[n]=template name + * eventdata[n]=template specific data + */ +static int ima_measurements_show(struct seq_file *m, void *v) +{ + /* the list never shrinks, so we don't need a lock here */ + struct ima_queue_entry *qe = v; + struct ima_template_entry *e; + int namelen; + u32 pcr = CONFIG_IMA_MEASURE_PCR_IDX; + + /* get entry */ + e = qe->entry; + if (e == NULL) + return -1; + + /* + * 1st: PCRIndex + * PCR used is always the same (config option) in + * little-endian format + */ + ima_putc(m, &pcr, sizeof pcr); + + /* 2nd: template digest */ + ima_putc(m, e->digest, IMA_DIGEST_SIZE); + + /* 3rd: template name size */ + namelen = strlen(e->template_name); + ima_putc(m, &namelen, sizeof namelen); + + /* 4th: template name */ + ima_putc(m, e->template_name, namelen); + + /* 5th: template specific data */ + ima_template_show(m, (struct ima_template_data *)&e->template, + IMA_SHOW_BINARY); + return 0; +} + +static struct seq_operations ima_measurments_seqops = { + .start = ima_measurements_start, + .next = ima_measurements_next, + .stop = ima_measurements_stop, + .show = ima_measurements_show +}; + +static int ima_measurements_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &ima_measurments_seqops); +} + +static struct file_operations ima_measurements_ops = { + .open = ima_measurements_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void ima_print_digest(struct seq_file *m, u8 *digest) +{ + int i; + + for (i = 0; i < IMA_DIGEST_SIZE; i++) + seq_printf(m, "%02x", *(digest + i)); +} + +void ima_template_show(struct seq_file *m, void *e, enum ima_show_type show) +{ + struct ima_template_data *entry = e; + int namelen; + + switch (show) { + case IMA_SHOW_ASCII: + ima_print_digest(m, entry->digest); + seq_printf(m, " %s\n", entry->file_name); + break; + case IMA_SHOW_BINARY: + ima_putc(m, entry->digest, IMA_DIGEST_SIZE); + + namelen = strlen(entry->file_name); + ima_putc(m, &namelen, sizeof namelen); + ima_putc(m, entry->file_name, namelen); + default: + break; + } +} + +/* print in ascii */ +static int ima_ascii_measurements_show(struct seq_file *m, void *v) +{ + /* the list never shrinks, so we don't need a lock here */ + struct ima_queue_entry *qe = v; + struct ima_template_entry *e; + + /* get entry */ + e = qe->entry; + if (e == NULL) + return -1; + + /* 1st: PCR used (config option) */ + seq_printf(m, "%2d ", CONFIG_IMA_MEASURE_PCR_IDX); + + /* 2nd: SHA1 template hash */ + ima_print_digest(m, e->digest); + + /* 3th: template name */ + seq_printf(m, " %s ", e->template_name); + + /* 4th: template specific data */ + ima_template_show(m, (struct ima_template_data *)&e->template, + IMA_SHOW_ASCII); + return 0; +} + +static struct seq_operations ima_ascii_measurements_seqops = { + .start = ima_measurements_start, + .next = ima_measurements_next, + .stop = ima_measurements_stop, + .show = ima_ascii_measurements_show +}; + +static int ima_ascii_measurements_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &ima_ascii_measurements_seqops); +} + +static struct file_operations ima_ascii_measurements_ops = { + .open = ima_ascii_measurements_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static ssize_t ima_write_policy(struct file *file, const char __user *buf, + size_t datalen, loff_t *ppos) +{ + char *data; + int rc; + + if (datalen >= PAGE_SIZE) + return -ENOMEM; + if (*ppos != 0) { + /* No partial writes. */ + return -EINVAL; + } + data = kmalloc(datalen + 1, GFP_KERNEL); + if (!data) + return -ENOMEM; + + if (copy_from_user(data, buf, datalen)) { + kfree(data); + return -EFAULT; + } + *(data + datalen) = '\0'; + rc = ima_parse_add_rule(data); + if (rc < 0) { + datalen = -EINVAL; + valid_policy = 0; + } + + kfree(data); + return datalen; +} + +static struct dentry *ima_dir; +static struct dentry *binary_runtime_measurements; +static struct dentry *ascii_runtime_measurements; +static struct dentry *runtime_measurements_count; +static struct dentry *violations; +static struct dentry *ima_policy; + +static atomic_t policy_opencount = ATOMIC_INIT(1); +/* + * ima_open_policy: sequentialize access to the policy file + */ +int ima_open_policy(struct inode * inode, struct file * filp) +{ + if (atomic_dec_and_test(&policy_opencount)) + return 0; + return -EBUSY; +} + +/* + * ima_release_policy - start using the new measure policy rules. + * + * Initially, ima_measure points to the default policy rules, now + * point to the new policy rules, and remove the securityfs policy file, + * assuming a valid policy. + */ +static int ima_release_policy(struct inode *inode, struct file *file) +{ + if (!valid_policy) { + ima_delete_rules(); + valid_policy = 1; + atomic_set(&policy_opencount, 1); + return 0; + } + ima_update_policy(); + securityfs_remove(ima_policy); + ima_policy = NULL; + return 0; +} + +static struct file_operations ima_measure_policy_ops = { + .open = ima_open_policy, + .write = ima_write_policy, + .release = ima_release_policy +}; + +int ima_fs_init(void) +{ + ima_dir = securityfs_create_dir("ima", NULL); + if (IS_ERR(ima_dir)) + return -1; + + binary_runtime_measurements = + securityfs_create_file("binary_runtime_measurements", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_measurements_ops); + if (IS_ERR(binary_runtime_measurements)) + goto out; + + ascii_runtime_measurements = + securityfs_create_file("ascii_runtime_measurements", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_ascii_measurements_ops); + if (IS_ERR(ascii_runtime_measurements)) + goto out; + + runtime_measurements_count = + securityfs_create_file("runtime_measurements_count", + S_IRUSR | S_IRGRP, ima_dir, NULL, + &ima_measurements_count_ops); + if (IS_ERR(runtime_measurements_count)) + goto out; + + violations = + securityfs_create_file("violations", S_IRUSR | S_IRGRP, + ima_dir, NULL, &ima_htable_violations_ops); + if (IS_ERR(violations)) + goto out; + + ima_policy = securityfs_create_file("policy", + S_IRUSR | S_IRGRP | S_IWUSR, + ima_dir, NULL, + &ima_measure_policy_ops); + if (IS_ERR(ima_policy)) + goto out; + + return 0; +out: + securityfs_remove(runtime_measurements_count); + securityfs_remove(ascii_runtime_measurements); + securityfs_remove(binary_runtime_measurements); + securityfs_remove(ima_dir); + securityfs_remove(ima_policy); + return -1; +} + +void __exit ima_fs_cleanup(void) +{ + securityfs_remove(violations); + securityfs_remove(runtime_measurements_count); + securityfs_remove(ascii_runtime_measurements); + securityfs_remove(binary_runtime_measurements); + securityfs_remove(ima_dir); + securityfs_remove(ima_policy); +} diff --git a/security/integrity/ima/ima_iint.c b/security/integrity/ima/ima_iint.c new file mode 100644 index 000000000000..1f035e8d29c7 --- /dev/null +++ b/security/integrity/ima/ima_iint.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2008 IBM Corporation + * + * Authors: + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_iint.c + * - implements the IMA hooks: ima_inode_alloc, ima_inode_free + * - cache integrity information associated with an inode + * using a radix tree. + */ +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/radix-tree.h> +#include "ima.h" + +#define ima_iint_delete ima_inode_free + +RADIX_TREE(ima_iint_store, GFP_ATOMIC); +DEFINE_SPINLOCK(ima_iint_lock); + +static struct kmem_cache *iint_cache __read_mostly; + +/* ima_iint_find_get - return the iint associated with an inode + * + * ima_iint_find_get gets a reference to the iint. Caller must + * remember to put the iint reference. + */ +struct ima_iint_cache *ima_iint_find_get(struct inode *inode) +{ + struct ima_iint_cache *iint; + + rcu_read_lock(); + iint = radix_tree_lookup(&ima_iint_store, (unsigned long)inode); + if (!iint) + goto out; + kref_get(&iint->refcount); +out: + rcu_read_unlock(); + return iint; +} + +/* Allocate memory for the iint associated with the inode + * from the iint_cache slab, initialize the iint, and + * insert it into the radix tree. + * + * On success return a pointer to the iint; on failure return NULL. + */ +struct ima_iint_cache *ima_iint_insert(struct inode *inode) +{ + struct ima_iint_cache *iint = NULL; + int rc = 0; + + if (!ima_initialized) + return iint; + iint = kmem_cache_alloc(iint_cache, GFP_KERNEL); + if (!iint) + return iint; + + rc = radix_tree_preload(GFP_KERNEL); + if (rc < 0) + goto out; + + spin_lock(&ima_iint_lock); + rc = radix_tree_insert(&ima_iint_store, (unsigned long)inode, iint); + spin_unlock(&ima_iint_lock); +out: + if (rc < 0) { + kmem_cache_free(iint_cache, iint); + if (rc == -EEXIST) { + iint = radix_tree_lookup(&ima_iint_store, + (unsigned long)inode); + } else + iint = NULL; + } + radix_tree_preload_end(); + return iint; +} + +/** + * ima_inode_alloc - allocate an iint associated with an inode + * @inode: pointer to the inode + * + * Return 0 on success, 1 on failure. + */ +int ima_inode_alloc(struct inode *inode) +{ + struct ima_iint_cache *iint; + + if (!ima_initialized) + return 0; + + iint = ima_iint_insert(inode); + if (!iint) + return 1; + return 0; +} + +/* ima_iint_find_insert_get - get the iint associated with an inode + * + * Most insertions are done at inode_alloc, except those allocated + * before late_initcall. When the iint does not exist, allocate it, + * initialize and insert it, and increment the iint refcount. + * + * (Can't initialize at security_initcall before any inodes are + * allocated, got to wait at least until proc_init.) + * + * Return the iint. + */ +struct ima_iint_cache *ima_iint_find_insert_get(struct inode *inode) +{ + struct ima_iint_cache *iint = NULL; + + iint = ima_iint_find_get(inode); + if (iint) + return iint; + + iint = ima_iint_insert(inode); + if (iint) + kref_get(&iint->refcount); + + return iint; +} +EXPORT_SYMBOL_GPL(ima_iint_find_insert_get); + +/* iint_free - called when the iint refcount goes to zero */ +void iint_free(struct kref *kref) +{ + struct ima_iint_cache *iint = container_of(kref, struct ima_iint_cache, + refcount); + iint->version = 0; + iint->flags = 0UL; + if (iint->readcount != 0) { + printk(KERN_INFO "%s: readcount: %ld\n", __FUNCTION__, + iint->readcount); + iint->readcount = 0; + } + if (iint->writecount != 0) { + printk(KERN_INFO "%s: writecount: %ld\n", __FUNCTION__, + iint->writecount); + iint->writecount = 0; + } + if (iint->opencount != 0) { + printk(KERN_INFO "%s: opencount: %ld\n", __FUNCTION__, + iint->opencount); + iint->opencount = 0; + } + kref_set(&iint->refcount, 1); + kmem_cache_free(iint_cache, iint); +} + +void iint_rcu_free(struct rcu_head *rcu_head) +{ + struct ima_iint_cache *iint = container_of(rcu_head, + struct ima_iint_cache, rcu); + kref_put(&iint->refcount, iint_free); +} + +/** + * ima_iint_delete - called on integrity_inode_free + * @inode: pointer to the inode + * + * Free the integrity information(iint) associated with an inode. + */ +void ima_iint_delete(struct inode *inode) +{ + struct ima_iint_cache *iint; + + if (!ima_initialized) + return; + spin_lock(&ima_iint_lock); + iint = radix_tree_delete(&ima_iint_store, (unsigned long)inode); + spin_unlock(&ima_iint_lock); + if (iint) + call_rcu(&iint->rcu, iint_rcu_free); +} + +static void init_once(void *foo) +{ + struct ima_iint_cache *iint = foo; + + memset(iint, 0, sizeof *iint); + iint->version = 0; + iint->flags = 0UL; + mutex_init(&iint->mutex); + iint->readcount = 0; + iint->writecount = 0; + iint->opencount = 0; + kref_set(&iint->refcount, 1); +} + +void ima_iintcache_init(void) +{ + iint_cache = + kmem_cache_create("iint_cache", sizeof(struct ima_iint_cache), 0, + SLAB_PANIC, init_once); +} diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c new file mode 100644 index 000000000000..cf227dbfac2c --- /dev/null +++ b/security/integrity/ima/ima_init.c @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer <sailer@watson.ibm.com> + * Leendert van Doorn <leendert@watson.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_init.c + * initialization and cleanup functions + */ +#include <linux/module.h> +#include <linux/scatterlist.h> +#include <linux/err.h> +#include "ima.h" + +/* name for boot aggregate entry */ +static char *boot_aggregate_name = "boot_aggregate"; +int ima_used_chip; + +/* Add the boot aggregate to the IMA measurement list and extend + * the PCR register. + * + * Calculate the boot aggregate, a SHA1 over tpm registers 0-7, + * assuming a TPM chip exists, and zeroes if the TPM chip does not + * exist. Add the boot aggregate measurement to the measurement + * list and extend the PCR register. + * + * If a tpm chip does not exist, indicate the core root of trust is + * not hardware based by invalidating the aggregate PCR value. + * (The aggregate PCR value is invalidated by adding one value to + * the measurement list and extending the aggregate PCR value with + * a different value.) Violations add a zero entry to the measurement + * list and extend the aggregate PCR value with ff...ff's. + */ +static void ima_add_boot_aggregate(void) +{ + struct ima_template_entry *entry; + const char *op = "add_boot_aggregate"; + const char *audit_cause = "ENOMEM"; + int result = -ENOMEM; + int violation = 1; + + entry = kmalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) + goto err_out; + + memset(&entry->template, 0, sizeof(entry->template)); + strncpy(entry->template.file_name, boot_aggregate_name, + IMA_EVENT_NAME_LEN_MAX); + if (ima_used_chip) { + violation = 0; + result = ima_calc_boot_aggregate(entry->template.digest); + if (result < 0) { + audit_cause = "hashing_error"; + kfree(entry); + goto err_out; + } + } + result = ima_store_template(entry, violation, NULL); + if (result < 0) + kfree(entry); + return; +err_out: + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, boot_aggregate_name, op, + audit_cause, result, 0); +} + +int ima_init(void) +{ + u8 pcr_i[IMA_DIGEST_SIZE]; + int rc; + + ima_used_chip = 0; + rc = tpm_pcr_read(TPM_ANY_NUM, 0, pcr_i); + if (rc == 0) + ima_used_chip = 1; + + if (!ima_used_chip) + pr_info("No TPM chip found, activating TPM-bypass!\n"); + + ima_add_boot_aggregate(); /* boot aggregate must be first entry */ + ima_init_policy(); + + return ima_fs_init(); +} + +void __exit ima_cleanup(void) +{ + ima_fs_cleanup(); +} diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c new file mode 100644 index 000000000000..f4e7266f5aee --- /dev/null +++ b/security/integrity/ima/ima_main.c @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Reiner Sailer <sailer@watson.ibm.com> + * Serge Hallyn <serue@us.ibm.com> + * Kylene Hall <kylene@us.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_main.c + * implements the IMA hooks: ima_bprm_check, ima_file_mmap, + * and ima_path_check. + */ +#include <linux/module.h> +#include <linux/file.h> +#include <linux/binfmts.h> +#include <linux/mount.h> +#include <linux/mman.h> + +#include "ima.h" + +int ima_initialized; + +char *ima_hash = "sha1"; +static int __init hash_setup(char *str) +{ + const char *op = "hash_setup"; + const char *hash = "sha1"; + int result = 0; + int audit_info = 0; + + if (strncmp(str, "md5", 3) == 0) { + hash = "md5"; + ima_hash = str; + } else if (strncmp(str, "sha1", 4) != 0) { + hash = "invalid_hash_type"; + result = 1; + } + integrity_audit_msg(AUDIT_INTEGRITY_HASH, NULL, NULL, op, hash, + result, audit_info); + return 1; +} +__setup("ima_hash=", hash_setup); + +/** + * ima_file_free - called on __fput() + * @file: pointer to file structure being freed + * + * Flag files that changed, based on i_version; + * and decrement the iint readcount/writecount. + */ +void ima_file_free(struct file *file) +{ + struct inode *inode = file->f_dentry->d_inode; + struct ima_iint_cache *iint; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return; + iint = ima_iint_find_get(inode); + if (!iint) + return; + + mutex_lock(&iint->mutex); + if (iint->opencount <= 0) { + printk(KERN_INFO + "%s: %s open/free imbalance (r:%ld w:%ld o:%ld f:%ld)\n", + __FUNCTION__, file->f_dentry->d_name.name, + iint->readcount, iint->writecount, + iint->opencount, atomic_long_read(&file->f_count)); + if (!(iint->flags & IMA_IINT_DUMP_STACK)) { + dump_stack(); + iint->flags |= IMA_IINT_DUMP_STACK; + } + } + iint->opencount--; + + if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) + iint->readcount--; + + if (file->f_mode & FMODE_WRITE) { + iint->writecount--; + if (iint->writecount == 0) { + if (iint->version != inode->i_version) + iint->flags &= ~IMA_MEASURED; + } + } + mutex_unlock(&iint->mutex); + kref_put(&iint->refcount, iint_free); +} + +/* ima_read_write_check - reflect possible reading/writing errors in the PCR. + * + * When opening a file for read, if the file is already open for write, + * the file could change, resulting in a file measurement error. + * + * Opening a file for write, if the file is already open for read, results + * in a time of measure, time of use (ToMToU) error. + * + * In either case invalidate the PCR. + */ +enum iint_pcr_error { TOMTOU, OPEN_WRITERS }; +static void ima_read_write_check(enum iint_pcr_error error, + struct ima_iint_cache *iint, + struct inode *inode, + const unsigned char *filename) +{ + switch (error) { + case TOMTOU: + if (iint->readcount > 0) + ima_add_violation(inode, filename, "invalid_pcr", + "ToMToU"); + break; + case OPEN_WRITERS: + if (iint->writecount > 0) + ima_add_violation(inode, filename, "invalid_pcr", + "open_writers"); + break; + } +} + +static int get_path_measurement(struct ima_iint_cache *iint, struct file *file, + const unsigned char *filename) +{ + int rc = 0; + + if (IS_ERR(file)) { + pr_info("%s dentry_open failed\n", filename); + return rc; + } + iint->opencount++; + iint->readcount++; + + rc = ima_collect_measurement(iint, file); + if (!rc) + ima_store_measurement(iint, file, filename); + return rc; +} + +/** + * ima_path_check - based on policy, collect/store measurement. + * @path: contains a pointer to the path to be measured + * @mask: contains MAY_READ, MAY_WRITE or MAY_EXECUTE + * + * Measure the file being open for readonly, based on the + * ima_must_measure() policy decision. + * + * Keep read/write counters for all files, but only + * invalidate the PCR for measured files: + * - Opening a file for write when already open for read, + * results in a time of measure, time of use (ToMToU) error. + * - Opening a file for read when already open for write, + * could result in a file measurement error. + * + * Return 0 on success, an error code on failure. + * (Based on the results of appraise_measurement().) + */ +int ima_path_check(struct path *path, int mask) +{ + struct inode *inode = path->dentry->d_inode; + struct ima_iint_cache *iint; + struct file *file = NULL; + int rc; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return 0; + iint = ima_iint_find_insert_get(inode); + if (!iint) + return 0; + + mutex_lock(&iint->mutex); + iint->opencount++; + if ((mask & MAY_WRITE) || (mask == 0)) + iint->writecount++; + else if (mask & (MAY_READ | MAY_EXEC)) + iint->readcount++; + + rc = ima_must_measure(iint, inode, MAY_READ, PATH_CHECK); + if (rc < 0) + goto out; + + if ((mask & MAY_WRITE) || (mask == 0)) + ima_read_write_check(TOMTOU, iint, inode, + path->dentry->d_name.name); + + if ((mask & (MAY_WRITE | MAY_READ | MAY_EXEC)) != MAY_READ) + goto out; + + ima_read_write_check(OPEN_WRITERS, iint, inode, + path->dentry->d_name.name); + if (!(iint->flags & IMA_MEASURED)) { + struct dentry *dentry = dget(path->dentry); + struct vfsmount *mnt = mntget(path->mnt); + + file = dentry_open(dentry, mnt, O_RDONLY, current->cred); + rc = get_path_measurement(iint, file, dentry->d_name.name); + } +out: + mutex_unlock(&iint->mutex); + if (file) + fput(file); + kref_put(&iint->refcount, iint_free); + return 0; +} + +static int process_measurement(struct file *file, const unsigned char *filename, + int mask, int function) +{ + struct inode *inode = file->f_dentry->d_inode; + struct ima_iint_cache *iint; + int rc; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return 0; + iint = ima_iint_find_insert_get(inode); + if (!iint) + return -ENOMEM; + + mutex_lock(&iint->mutex); + rc = ima_must_measure(iint, inode, mask, function); + if (rc != 0) + goto out; + + rc = ima_collect_measurement(iint, file); + if (!rc) + ima_store_measurement(iint, file, filename); +out: + mutex_unlock(&iint->mutex); + kref_put(&iint->refcount, iint_free); + return rc; +} + +static void opencount_get(struct file *file) +{ + struct inode *inode = file->f_dentry->d_inode; + struct ima_iint_cache *iint; + + if (!ima_initialized || !S_ISREG(inode->i_mode)) + return; + iint = ima_iint_find_insert_get(inode); + if (!iint) + return; + mutex_lock(&iint->mutex); + iint->opencount++; + mutex_unlock(&iint->mutex); +} + +/** + * ima_file_mmap - based on policy, collect/store measurement. + * @file: pointer to the file to be measured (May be NULL) + * @prot: contains the protection that will be applied by the kernel. + * + * Measure files being mmapped executable based on the ima_must_measure() + * policy decision. + * + * Return 0 on success, an error code on failure. + * (Based on the results of appraise_measurement().) + */ +int ima_file_mmap(struct file *file, unsigned long prot) +{ + int rc; + + if (!file) + return 0; + if (prot & PROT_EXEC) + rc = process_measurement(file, file->f_dentry->d_name.name, + MAY_EXEC, FILE_MMAP); + return 0; +} + +/* + * ima_shm_check - IPC shm and shmat create/fput a file + * + * Maintain the opencount for these files to prevent unnecessary + * imbalance messages. + */ +void ima_shm_check(struct file *file) +{ + opencount_get(file); + return; +} + +/** + * ima_bprm_check - based on policy, collect/store measurement. + * @bprm: contains the linux_binprm structure + * + * The OS protects against an executable file, already open for write, + * from being executed in deny_write_access() and an executable file, + * already open for execute, from being modified in get_write_access(). + * So we can be certain that what we verify and measure here is actually + * what is being executed. + * + * Return 0 on success, an error code on failure. + * (Based on the results of appraise_measurement().) + */ +int ima_bprm_check(struct linux_binprm *bprm) +{ + int rc; + + rc = process_measurement(bprm->file, bprm->filename, + MAY_EXEC, BPRM_CHECK); + return 0; +} + +static int __init init_ima(void) +{ + int error; + + ima_iintcache_init(); + error = ima_init(); + ima_initialized = 1; + return error; +} + +static void __exit cleanup_ima(void) +{ + ima_cleanup(); +} + +late_initcall(init_ima); /* Start IMA after the TPM is available */ + +MODULE_DESCRIPTION("Integrity Measurement Architecture"); +MODULE_LICENSE("GPL"); diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c new file mode 100644 index 000000000000..23810e0bfc68 --- /dev/null +++ b/security/integrity/ima/ima_policy.c @@ -0,0 +1,413 @@ +/* + * Copyright (C) 2008 IBM Corporation + * Author: Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * ima_policy.c + * - initialize default measure policy rules + * + */ +#include <linux/module.h> +#include <linux/list.h> +#include <linux/audit.h> +#include <linux/security.h> +#include <linux/magic.h> +#include <linux/parser.h> + +#include "ima.h" + +/* flags definitions */ +#define IMA_FUNC 0x0001 +#define IMA_MASK 0x0002 +#define IMA_FSMAGIC 0x0004 +#define IMA_UID 0x0008 + +enum ima_action { UNKNOWN = -1, DONT_MEASURE = 0, MEASURE }; + +#define MAX_LSM_RULES 6 +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 list_head list; + enum ima_action action; + unsigned int flags; + enum ima_hooks func; + int mask; + unsigned long fsmagic; + uid_t uid; + struct { + void *rule; /* LSM file metadata specific */ + int type; /* audit type */ + } lsm[MAX_LSM_RULES]; +}; + +/* Without LSM specific knowledge, the default policy can only be + * written in terms of .action, .func, .mask, .fsmagic, and .uid + */ +static struct ima_measure_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}, + {.action = DONT_MEASURE,.fsmagic = TMPFS_MAGIC,.flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = SECURITYFS_MAGIC, + .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE,.fsmagic = 0xF97CFF8C,.flags = IMA_FSMAGIC}, + {.action = MEASURE,.func = FILE_MMAP,.mask = MAY_EXEC, + .flags = IMA_FUNC | IMA_MASK}, + {.action = MEASURE,.func = BPRM_CHECK,.mask = MAY_EXEC, + .flags = IMA_FUNC | IMA_MASK}, + {.action = MEASURE,.func = PATH_CHECK,.mask = MAY_READ,.uid = 0, + .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 DEFINE_MUTEX(ima_measure_mutex); + +/** + * ima_match_rules - determine whether an inode matches the measure rule. + * @rule: a pointer to a rule + * @inode: a pointer to an inode + * @func: LIM hook identifier + * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * + * Returns true on rule match, false on failure. + */ +static bool ima_match_rules(struct ima_measure_rule_entry *rule, + struct inode *inode, enum ima_hooks func, int mask) +{ + struct task_struct *tsk = current; + int i; + + if ((rule->flags & IMA_FUNC) && rule->func != func) + return false; + if ((rule->flags & IMA_MASK) && rule->mask != mask) + return false; + if ((rule->flags & IMA_FSMAGIC) + && rule->fsmagic != inode->i_sb->s_magic) + return false; + if ((rule->flags & IMA_UID) && rule->uid != tsk->cred->uid) + return false; + for (i = 0; i < MAX_LSM_RULES; i++) { + int rc; + u32 osid, sid; + + if (!rule->lsm[i].rule) + continue; + + switch (i) { + case LSM_OBJ_USER: + case LSM_OBJ_ROLE: + case LSM_OBJ_TYPE: + security_inode_getsecid(inode, &osid); + rc = security_filter_rule_match(osid, + rule->lsm[i].type, + AUDIT_EQUAL, + rule->lsm[i].rule, + NULL); + break; + case LSM_SUBJ_USER: + case LSM_SUBJ_ROLE: + case LSM_SUBJ_TYPE: + security_task_getsecid(tsk, &sid); + rc = security_filter_rule_match(sid, + rule->lsm[i].type, + AUDIT_EQUAL, + rule->lsm[i].rule, + NULL); + default: + break; + } + if (!rc) + return false; + } + return true; +} + +/** + * ima_match_policy - decision based on LSM and other conditions + * @inode: pointer to an inode for which the policy decision is being made + * @func: IMA hook identifier + * @mask: requested action (MAY_READ | MAY_WRITE | MAY_APPEND | MAY_EXEC) + * + * Measure decision based on func/mask/fsmagic and LSM(subj/obj/type) + * conditions. + * + * (There is no need for locking when walking the policy list, + * 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) +{ + struct ima_measure_rule_entry *entry; + + list_for_each_entry(entry, ima_measure, list) { + bool rc; + + rc = ima_match_rules(entry, inode, func, mask); + if (rc) + return entry->action; + } + return 0; +} + +/** + * ima_init_policy - initialize the default measure rules. + * + * ima_measure points to either the measure_default_rules or the + * the new measure_policy_rules. + */ +void ima_init_policy(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(default_rules); i++) + list_add_tail(&default_rules[i].list, &measure_default_rules); + ima_measure = &measure_default_rules; +} + +/** + * ima_update_policy - update default_rules with new measure rules + * + * Called on file .release to update the default rules with a complete new + * policy. Once updated, the policy is locked, no additional rules can be + * added to the policy. + */ +void ima_update_policy(void) +{ + const char *op = "policy_update"; + const char *cause = "already exists"; + int result = 1; + int audit_info = 0; + + if (ima_measure == &measure_default_rules) { + ima_measure = &measure_policy_rules; + cause = "complete"; + result = 0; + } + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, cause, result, audit_info); +} + +enum { + Opt_err = -1, + Opt_measure = 1, Opt_dont_measure, + 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 +}; + +static match_table_t policy_tokens = { + {Opt_measure, "measure"}, + {Opt_dont_measure, "dont_measure"}, + {Opt_obj_user, "obj_user=%s"}, + {Opt_obj_role, "obj_role=%s"}, + {Opt_obj_type, "obj_type=%s"}, + {Opt_subj_user, "subj_user=%s"}, + {Opt_subj_role, "subj_role=%s"}, + {Opt_subj_type, "subj_type=%s"}, + {Opt_func, "func=%s"}, + {Opt_mask, "mask=%s"}, + {Opt_fsmagic, "fsmagic=%s"}, + {Opt_uid, "uid=%s"}, + {Opt_err, NULL} +}; + +static int ima_lsm_rule_init(struct ima_measure_rule_entry *entry, + char *args, int lsm_rule, int audit_type) +{ + int result; + + entry->lsm[lsm_rule].type = audit_type; + result = security_filter_rule_init(entry->lsm[lsm_rule].type, + AUDIT_EQUAL, args, + &entry->lsm[lsm_rule].rule); + return result; +} + +static int ima_parse_rule(char *rule, struct ima_measure_rule_entry *entry) +{ + struct audit_buffer *ab; + char *p; + int result = 0; + + ab = audit_log_start(current->audit_context, GFP_KERNEL, + AUDIT_INTEGRITY_STATUS); + + entry->action = -1; + while ((p = strsep(&rule, " \n")) != NULL) { + substring_t args[MAX_OPT_ARGS]; + int token; + unsigned long lnum; + + if (result < 0) + break; + if (!*p) + continue; + token = match_token(p, policy_tokens, args); + switch (token) { + case Opt_measure: + audit_log_format(ab, "%s ", "measure"); + entry->action = MEASURE; + break; + case Opt_dont_measure: + audit_log_format(ab, "%s ", "dont_measure"); + entry->action = DONT_MEASURE; + break; + case Opt_func: + audit_log_format(ab, "func=%s ", args[0].from); + if (strcmp(args[0].from, "PATH_CHECK") == 0) + entry->func = PATH_CHECK; + else if (strcmp(args[0].from, "FILE_MMAP") == 0) + entry->func = FILE_MMAP; + else if (strcmp(args[0].from, "BPRM_CHECK") == 0) + entry->func = BPRM_CHECK; + else + result = -EINVAL; + if (!result) + entry->flags |= IMA_FUNC; + break; + case Opt_mask: + audit_log_format(ab, "mask=%s ", args[0].from); + if ((strcmp(args[0].from, "MAY_EXEC")) == 0) + entry->mask = MAY_EXEC; + else if (strcmp(args[0].from, "MAY_WRITE") == 0) + entry->mask = MAY_WRITE; + else if (strcmp(args[0].from, "MAY_READ") == 0) + entry->mask = MAY_READ; + else if (strcmp(args[0].from, "MAY_APPEND") == 0) + entry->mask = MAY_APPEND; + else + result = -EINVAL; + if (!result) + entry->flags |= IMA_MASK; + break; + case Opt_fsmagic: + audit_log_format(ab, "fsmagic=%s ", args[0].from); + result = strict_strtoul(args[0].from, 16, + &entry->fsmagic); + if (!result) + entry->flags |= IMA_FSMAGIC; + break; + case Opt_uid: + audit_log_format(ab, "uid=%s ", args[0].from); + result = strict_strtoul(args[0].from, 10, &lnum); + if (!result) { + entry->uid = (uid_t) lnum; + if (entry->uid != lnum) + result = -EINVAL; + else + entry->flags |= IMA_UID; + } + break; + case Opt_obj_user: + audit_log_format(ab, "obj_user=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_USER, + AUDIT_OBJ_USER); + break; + case Opt_obj_role: + audit_log_format(ab, "obj_role=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_ROLE, + AUDIT_OBJ_ROLE); + break; + case Opt_obj_type: + audit_log_format(ab, "obj_type=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_OBJ_TYPE, + AUDIT_OBJ_TYPE); + break; + case Opt_subj_user: + audit_log_format(ab, "subj_user=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_USER, + AUDIT_SUBJ_USER); + break; + case Opt_subj_role: + audit_log_format(ab, "subj_role=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_ROLE, + AUDIT_SUBJ_ROLE); + break; + case Opt_subj_type: + audit_log_format(ab, "subj_type=%s ", args[0].from); + result = ima_lsm_rule_init(entry, args[0].from, + LSM_SUBJ_TYPE, + AUDIT_SUBJ_TYPE); + break; + case Opt_err: + printk(KERN_INFO "%s: unknown token: %s\n", + __FUNCTION__, p); + break; + } + } + if (entry->action == UNKNOWN) + result = -EINVAL; + + audit_log_format(ab, "res=%d", result); + audit_log_end(ab); + return result; +} + +/** + * ima_parse_add_rule - add a rule to measure_policy_rules + * @rule - ima measurement policy rule + * + * Uses a mutex to protect the policy list from multiple concurrent writers. + * Returns 0 on success, an error code on failure. + */ +int ima_parse_add_rule(char *rule) +{ + const char *op = "add_rule"; + struct ima_measure_rule_entry *entry; + int result = 0; + int audit_info = 0; + + /* Prevent installed policy from changing */ + if (ima_measure != &measure_default_rules) { + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, "already exists", + -EACCES, audit_info); + return -EACCES; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, + NULL, op, "-ENOMEM", -ENOMEM, audit_info); + return -ENOMEM; + } + + INIT_LIST_HEAD(&entry->list); + + result = ima_parse_rule(rule, entry); + if (!result) { + mutex_lock(&ima_measure_mutex); + list_add_tail(&entry->list, &measure_policy_rules); + mutex_unlock(&ima_measure_mutex); + } else + kfree(entry); + return result; +} + +/* ima_delete_rules called to cleanup invalid policy */ +void ima_delete_rules(void) +{ + struct ima_measure_rule_entry *entry, *tmp; + + mutex_lock(&ima_measure_mutex); + list_for_each_entry_safe(entry, tmp, &measure_policy_rules, list) { + list_del(&entry->list); + kfree(entry); + } + mutex_unlock(&ima_measure_mutex); +} diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c new file mode 100644 index 000000000000..7ec94314ac0c --- /dev/null +++ b/security/integrity/ima/ima_queue.c @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2005,2006,2007,2008 IBM Corporation + * + * Authors: + * Serge Hallyn <serue@us.ibm.com> + * Reiner Sailer <sailer@watson.ibm.com> + * Mimi Zohar <zohar@us.ibm.com> + * + * 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. + * + * File: ima_queue.c + * Implements queues that store template measurements and + * maintains aggregate over the stored measurements + * in the pre-configured TPM PCR (if available). + * The measurement list is append-only. No entry is + * ever removed or changed during the boot-cycle. + */ +#include <linux/module.h> +#include <linux/rculist.h> +#include "ima.h" + +LIST_HEAD(ima_measurements); /* list of all measurements */ + +/* key: inode (before secure-hashing a file) */ +struct ima_h_table ima_htable = { + .len = ATOMIC_LONG_INIT(0), + .violations = ATOMIC_LONG_INIT(0), + .queue[0 ... IMA_MEASURE_HTABLE_SIZE - 1] = HLIST_HEAD_INIT +}; + +/* mutex protects atomicity of extending measurement list + * and extending the TPM PCR aggregate. Since tpm_extend can take + * long (and the tpm driver uses a mutex), we can't use the spinlock. + */ +static DEFINE_MUTEX(ima_extend_list_mutex); + +/* lookup up the digest value in the hash table, and return the entry */ +static struct ima_queue_entry *ima_lookup_digest_entry(u8 *digest_value) +{ + struct ima_queue_entry *qe, *ret = NULL; + unsigned int key; + struct hlist_node *pos; + int rc; + + key = ima_hash_key(digest_value); + rcu_read_lock(); + hlist_for_each_entry_rcu(qe, pos, &ima_htable.queue[key], hnext) { + rc = memcmp(qe->entry->digest, digest_value, IMA_DIGEST_SIZE); + if (rc == 0) { + ret = qe; + break; + } + } + rcu_read_unlock(); + return ret; +} + +/* ima_add_template_entry helper function: + * - Add template entry to measurement list and hash table. + * + * (Called with ima_extend_list_mutex held.) + */ +static int ima_add_digest_entry(struct ima_template_entry *entry) +{ + struct ima_queue_entry *qe; + unsigned int key; + + qe = kmalloc(sizeof(*qe), GFP_KERNEL); + if (qe == NULL) { + pr_err("OUT OF MEMORY ERROR creating queue entry.\n"); + return -ENOMEM; + } + qe->entry = entry; + + INIT_LIST_HEAD(&qe->later); + list_add_tail_rcu(&qe->later, &ima_measurements); + + atomic_long_inc(&ima_htable.len); + key = ima_hash_key(entry->digest); + hlist_add_head_rcu(&qe->hnext, &ima_htable.queue[key]); + return 0; +} + +static int ima_pcr_extend(const u8 *hash) +{ + int result = 0; + + if (!ima_used_chip) + return result; + + result = tpm_pcr_extend(TPM_ANY_NUM, CONFIG_IMA_MEASURE_PCR_IDX, hash); + if (result != 0) + pr_err("Error Communicating to TPM chip\n"); + return result; +} + +/* Add template entry to the measurement list and hash table, + * and extend the pcr. + */ +int ima_add_template_entry(struct ima_template_entry *entry, int violation, + const char *op, struct inode *inode) +{ + u8 digest[IMA_DIGEST_SIZE]; + const char *audit_cause = "hash_added"; + int audit_info = 1; + int result = 0; + + mutex_lock(&ima_extend_list_mutex); + if (!violation) { + memcpy(digest, entry->digest, sizeof digest); + if (ima_lookup_digest_entry(digest)) { + audit_cause = "hash_exists"; + goto out; + } + } + + result = ima_add_digest_entry(entry); + if (result < 0) { + audit_cause = "ENOMEM"; + audit_info = 0; + goto out; + } + + if (violation) /* invalidate pcr */ + memset(digest, 0xff, sizeof digest); + + result = ima_pcr_extend(digest); + if (result != 0) { + audit_cause = "TPM error"; + audit_info = 0; + } +out: + mutex_unlock(&ima_extend_list_mutex); + integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, entry->template_name, + op, audit_cause, result, audit_info); + return result; +} diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 00815973d412..a69d6f8970ca 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -89,7 +89,7 @@ #define XATTR_SELINUX_SUFFIX "selinux" #define XATTR_NAME_SELINUX XATTR_SECURITY_PREFIX XATTR_SELINUX_SUFFIX -#define NUM_SEL_MNT_OPTS 4 +#define NUM_SEL_MNT_OPTS 5 extern unsigned int policydb_loaded_version; extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm); @@ -353,6 +353,7 @@ enum { Opt_fscontext = 2, Opt_defcontext = 3, Opt_rootcontext = 4, + Opt_labelsupport = 5, }; static const match_table_t tokens = { @@ -360,6 +361,7 @@ static const match_table_t tokens = { {Opt_fscontext, FSCONTEXT_STR "%s"}, {Opt_defcontext, DEFCONTEXT_STR "%s"}, {Opt_rootcontext, ROOTCONTEXT_STR "%s"}, + {Opt_labelsupport, LABELSUPP_STR}, {Opt_error, NULL}, }; @@ -431,7 +433,7 @@ static int sb_finish_set_opts(struct super_block *sb) } } - sbsec->initialized = 1; + sbsec->flags |= (SE_SBINITIALIZED | SE_SBLABELSUPP); if (sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) printk(KERN_ERR "SELinux: initialized (dev %s, type %s), unknown behavior\n", @@ -441,6 +443,12 @@ static int sb_finish_set_opts(struct super_block *sb) sb->s_id, sb->s_type->name, labeling_behaviors[sbsec->behavior-1]); + if (sbsec->behavior == SECURITY_FS_USE_GENFS || + sbsec->behavior == SECURITY_FS_USE_MNTPOINT || + sbsec->behavior == SECURITY_FS_USE_NONE || + sbsec->behavior > ARRAY_SIZE(labeling_behaviors)) + sbsec->flags &= ~SE_SBLABELSUPP; + /* Initialize the root inode. */ rc = inode_doinit_with_dentry(root_inode, root); @@ -487,23 +495,22 @@ static int selinux_get_mnt_opts(const struct super_block *sb, security_init_mnt_opts(opts); - if (!sbsec->initialized) + if (!(sbsec->flags & SE_SBINITIALIZED)) return -EINVAL; if (!ss_initialized) return -EINVAL; - /* - * if we ever use sbsec flags for anything other than tracking mount - * settings this is going to need a mask - */ - tmp = sbsec->flags; + tmp = sbsec->flags & SE_MNTMASK; /* count the number of mount options for this sb */ for (i = 0; i < 8; i++) { if (tmp & 0x01) opts->num_mnt_opts++; tmp >>= 1; } + /* Check if the Label support flag is set */ + if (sbsec->flags & SE_SBLABELSUPP) + opts->num_mnt_opts++; opts->mnt_opts = kcalloc(opts->num_mnt_opts, sizeof(char *), GFP_ATOMIC); if (!opts->mnt_opts) { @@ -549,6 +556,10 @@ static int selinux_get_mnt_opts(const struct super_block *sb, opts->mnt_opts[i] = context; opts->mnt_opts_flags[i++] = ROOTCONTEXT_MNT; } + if (sbsec->flags & SE_SBLABELSUPP) { + opts->mnt_opts[i] = NULL; + opts->mnt_opts_flags[i++] = SE_SBLABELSUPP; + } BUG_ON(i != opts->num_mnt_opts); @@ -562,8 +573,10 @@ out_free: static int bad_option(struct superblock_security_struct *sbsec, char flag, u32 old_sid, u32 new_sid) { + char mnt_flags = sbsec->flags & SE_MNTMASK; + /* check if the old mount command had the same options */ - if (sbsec->initialized) + if (sbsec->flags & SE_SBINITIALIZED) if (!(sbsec->flags & flag) || (old_sid != new_sid)) return 1; @@ -571,8 +584,8 @@ static int bad_option(struct superblock_security_struct *sbsec, char flag, /* check if we were passed the same options twice, * aka someone passed context=a,context=b */ - if (!sbsec->initialized) - if (sbsec->flags & flag) + if (!(sbsec->flags & SE_SBINITIALIZED)) + if (mnt_flags & flag) return 1; return 0; } @@ -626,7 +639,7 @@ static int selinux_set_mnt_opts(struct super_block *sb, * this sb does not set any security options. (The first options * will be used for both mounts) */ - if (sbsec->initialized && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) + if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) && (num_opts == 0)) goto out; @@ -637,6 +650,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, */ for (i = 0; i < num_opts; i++) { u32 sid; + + if (flags[i] == SE_SBLABELSUPP) + continue; rc = security_context_to_sid(mount_options[i], strlen(mount_options[i]), &sid); if (rc) { @@ -690,19 +706,19 @@ static int selinux_set_mnt_opts(struct super_block *sb, } } - if (sbsec->initialized) { + if (sbsec->flags & SE_SBINITIALIZED) { /* previously mounted with options, but not on this attempt? */ - if (sbsec->flags && !num_opts) + if ((sbsec->flags & SE_MNTMASK) && !num_opts) goto out_double_mount; rc = 0; goto out; } if (strcmp(sb->s_type->name, "proc") == 0) - sbsec->proc = 1; + sbsec->flags |= SE_SBPROC; /* Determine the labeling behavior to use for this filesystem type. */ - rc = security_fs_use(sbsec->proc ? "proc" : sb->s_type->name, &sbsec->behavior, &sbsec->sid); + rc = security_fs_use((sbsec->flags & SE_SBPROC) ? "proc" : sb->s_type->name, &sbsec->behavior, &sbsec->sid); if (rc) { printk(KERN_WARNING "%s: security_fs_use(%s) returned %d\n", __func__, sb->s_type->name, rc); @@ -806,10 +822,10 @@ static void selinux_sb_clone_mnt_opts(const struct super_block *oldsb, } /* how can we clone if the old one wasn't set up?? */ - BUG_ON(!oldsbsec->initialized); + BUG_ON(!(oldsbsec->flags & SE_SBINITIALIZED)); /* if fs is reusing a sb, just let its options stand... */ - if (newsbsec->initialized) + if (newsbsec->flags & SE_SBINITIALIZED) return; mutex_lock(&newsbsec->lock); @@ -917,7 +933,8 @@ static int selinux_parse_opts_str(char *options, goto out_err; } break; - + case Opt_labelsupport: + break; default: rc = -EINVAL; printk(KERN_WARNING "SELinux: unknown mount option\n"); @@ -999,7 +1016,12 @@ static void selinux_write_opts(struct seq_file *m, char *prefix; for (i = 0; i < opts->num_mnt_opts; i++) { - char *has_comma = strchr(opts->mnt_opts[i], ','); + char *has_comma; + + if (opts->mnt_opts[i]) + has_comma = strchr(opts->mnt_opts[i], ','); + else + has_comma = NULL; switch (opts->mnt_opts_flags[i]) { case CONTEXT_MNT: @@ -1014,6 +1036,10 @@ static void selinux_write_opts(struct seq_file *m, case DEFCONTEXT_MNT: prefix = DEFCONTEXT_STR; break; + case SE_SBLABELSUPP: + seq_putc(m, ','); + seq_puts(m, LABELSUPP_STR); + continue; default: BUG(); }; @@ -1209,7 +1235,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent goto out_unlock; sbsec = inode->i_sb->s_security; - if (!sbsec->initialized) { + if (!(sbsec->flags & SE_SBINITIALIZED)) { /* Defer initialization until selinux_complete_init, after the initial policy is loaded and the security server is ready to handle calls. */ @@ -1326,7 +1352,7 @@ static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dent /* Default to the fs superblock SID. */ isec->sid = sbsec->sid; - if (sbsec->proc && !S_ISLNK(inode->i_mode)) { + if ((sbsec->flags & SE_SBPROC) && !S_ISLNK(inode->i_mode)) { struct proc_inode *proci = PROC_I(inode); if (proci->pde) { isec->sclass = inode_mode_to_security_class(inode->i_mode); @@ -1587,7 +1613,7 @@ static int may_create(struct inode *dir, if (rc) return rc; - if (!newsid || sbsec->behavior == SECURITY_FS_USE_MNTPOINT) { + if (!newsid || !(sbsec->flags & SE_SBLABELSUPP)) { rc = security_transition_sid(sid, dsec->sid, tclass, &newsid); if (rc) return rc; @@ -1866,6 +1892,16 @@ static int selinux_capset(struct cred *new, const struct cred *old, return cred_has_perm(old, new, PROCESS__SETCAP); } +/* + * (This comment used to live with the selinux_task_setuid hook, + * which was removed). + * + * Since setuid only affects the current process, and since the SELinux + * controls are not based on the Linux identity attributes, SELinux does not + * need to control this operation. However, SELinux does control the use of + * the CAP_SETUID and CAP_SETGID capabilities using the capable hook. + */ + static int selinux_capable(struct task_struct *tsk, const struct cred *cred, int cap, int audit) { @@ -2156,11 +2192,6 @@ static int selinux_bprm_set_creds(struct linux_binprm *bprm) return 0; } -static int selinux_bprm_check_security(struct linux_binprm *bprm) -{ - return secondary_ops->bprm_check_security(bprm); -} - static int selinux_bprm_secureexec(struct linux_binprm *bprm) { const struct cred *cred = current_cred(); @@ -2290,8 +2321,6 @@ static void selinux_bprm_committing_creds(struct linux_binprm *bprm) struct rlimit *rlim, *initrlim; int rc, i; - secondary_ops->bprm_committing_creds(bprm); - new_tsec = bprm->cred->security; if (new_tsec->sid == new_tsec->osid) return; @@ -2337,8 +2366,6 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm) int rc, i; unsigned long flags; - secondary_ops->bprm_committed_creds(bprm); - osid = tsec->osid; sid = tsec->sid; @@ -2400,7 +2427,8 @@ static inline int selinux_option(char *option, int len) return (match_prefix(CONTEXT_STR, sizeof(CONTEXT_STR)-1, option, len) || match_prefix(FSCONTEXT_STR, sizeof(FSCONTEXT_STR)-1, option, len) || match_prefix(DEFCONTEXT_STR, sizeof(DEFCONTEXT_STR)-1, option, len) || - match_prefix(ROOTCONTEXT_STR, sizeof(ROOTCONTEXT_STR)-1, option, len)); + match_prefix(ROOTCONTEXT_STR, sizeof(ROOTCONTEXT_STR)-1, option, len) || + match_prefix(LABELSUPP_STR, sizeof(LABELSUPP_STR)-1, option, len)); } static inline void take_option(char **to, char *from, int *first, int len) @@ -2513,11 +2541,6 @@ static int selinux_mount(char *dev_name, void *data) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->sb_mount(dev_name, path, type, flags, data); - if (rc) - return rc; if (flags & MS_REMOUNT) return superblock_has_perm(cred, path->mnt->mnt_sb, @@ -2530,11 +2553,6 @@ static int selinux_mount(char *dev_name, static int selinux_umount(struct vfsmount *mnt, int flags) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->sb_umount(mnt, flags); - if (rc) - return rc; return superblock_has_perm(cred, mnt->mnt_sb, FILESYSTEM__UNMOUNT, NULL); @@ -2570,7 +2588,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, sid = tsec->sid; newsid = tsec->create_sid; - if (!newsid || sbsec->behavior == SECURITY_FS_USE_MNTPOINT) { + if (!newsid || !(sbsec->flags & SE_SBLABELSUPP)) { rc = security_transition_sid(sid, dsec->sid, inode_mode_to_security_class(inode->i_mode), &newsid); @@ -2585,14 +2603,14 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir, } /* Possibly defer initialization to selinux_complete_init. */ - if (sbsec->initialized) { + if (sbsec->flags & SE_SBINITIALIZED) { struct inode_security_struct *isec = inode->i_security; isec->sclass = inode_mode_to_security_class(inode->i_mode); isec->sid = newsid; isec->initialized = 1; } - if (!ss_initialized || sbsec->behavior == SECURITY_FS_USE_MNTPOINT) + if (!ss_initialized || !(sbsec->flags & SE_SBLABELSUPP)) return -EOPNOTSUPP; if (name) { @@ -2622,21 +2640,11 @@ static int selinux_inode_create(struct inode *dir, struct dentry *dentry, int ma static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { - int rc; - - rc = secondary_ops->inode_link(old_dentry, dir, new_dentry); - if (rc) - return rc; return may_link(dir, old_dentry, MAY_LINK); } static int selinux_inode_unlink(struct inode *dir, struct dentry *dentry) { - int rc; - - rc = secondary_ops->inode_unlink(dir, dentry); - if (rc) - return rc; return may_link(dir, dentry, MAY_UNLINK); } @@ -2657,12 +2665,6 @@ static int selinux_inode_rmdir(struct inode *dir, struct dentry *dentry) static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev) { - int rc; - - rc = secondary_ops->inode_mknod(dir, dentry, mode, dev); - if (rc) - return rc; - return may_create(dir, dentry, inode_mode_to_security_class(mode)); } @@ -2682,22 +2684,13 @@ static int selinux_inode_readlink(struct dentry *dentry) static int selinux_inode_follow_link(struct dentry *dentry, struct nameidata *nameidata) { const struct cred *cred = current_cred(); - int rc; - rc = secondary_ops->inode_follow_link(dentry, nameidata); - if (rc) - return rc; return dentry_has_perm(cred, NULL, dentry, FILE__READ); } static int selinux_inode_permission(struct inode *inode, int mask) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->inode_permission(inode, mask); - if (rc) - return rc; if (!mask) { /* No permission to check. Existence test. */ @@ -2711,11 +2704,6 @@ static int selinux_inode_permission(struct inode *inode, int mask) static int selinux_inode_setattr(struct dentry *dentry, struct iattr *iattr) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->inode_setattr(dentry, iattr); - if (rc) - return rc; if (iattr->ia_valid & ATTR_FORCE) return 0; @@ -2769,7 +2757,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, return selinux_inode_setotherxattr(dentry, name); sbsec = inode->i_sb->s_security; - if (sbsec->behavior == SECURITY_FS_USE_MNTPOINT) + if (!(sbsec->flags & SE_SBLABELSUPP)) return -EOPNOTSUPP; if (!is_owner_or_cap(inode)) @@ -2931,16 +2919,6 @@ static int selinux_inode_listsecurity(struct inode *inode, char *buffer, size_t return len; } -static int selinux_inode_need_killpriv(struct dentry *dentry) -{ - return secondary_ops->inode_need_killpriv(dentry); -} - -static int selinux_inode_killpriv(struct dentry *dentry) -{ - return secondary_ops->inode_killpriv(dentry); -} - static void selinux_inode_getsecid(const struct inode *inode, u32 *secid) { struct inode_security_struct *isec = inode->i_security; @@ -3078,18 +3056,13 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, unsigned long prot) { const struct cred *cred = current_cred(); - int rc; - - rc = secondary_ops->file_mprotect(vma, reqprot, prot); - if (rc) - return rc; if (selinux_checkreqprot) prot = reqprot; #ifndef CONFIG_PPC32 if ((prot & PROT_EXEC) && !(vma->vm_flags & VM_EXEC)) { - rc = 0; + int rc = 0; if (vma->vm_start >= vma->vm_mm->start_brk && vma->vm_end <= vma->vm_mm->brk) { rc = cred_has_perm(cred, cred, PROCESS__EXECHEAP); @@ -3239,12 +3212,6 @@ static int selinux_dentry_open(struct file *file, const struct cred *cred) static int selinux_task_create(unsigned long clone_flags) { - int rc; - - rc = secondary_ops->task_create(clone_flags); - if (rc) - return rc; - return current_has_perm(current, PROCESS__FORK); } @@ -3278,14 +3245,6 @@ static int selinux_cred_prepare(struct cred *new, const struct cred *old, } /* - * commit new credentials - */ -static void selinux_cred_commit(struct cred *new, const struct cred *old) -{ - secondary_ops->cred_commit(new, old); -} - -/* * set the security data for a kernel service * - all the creation contexts are set to unlabelled */ @@ -3329,29 +3288,6 @@ static int selinux_kernel_create_files_as(struct cred *new, struct inode *inode) return 0; } -static int selinux_task_setuid(uid_t id0, uid_t id1, uid_t id2, int flags) -{ - /* Since setuid only affects the current process, and - since the SELinux controls are not based on the Linux - identity attributes, SELinux does not need to control - this operation. However, SELinux does control the use - of the CAP_SETUID and CAP_SETGID capabilities using the - capable hook. */ - return 0; -} - -static int selinux_task_fix_setuid(struct cred *new, const struct cred *old, - int flags) -{ - return secondary_ops->task_fix_setuid(new, old, flags); -} - -static int selinux_task_setgid(gid_t id0, gid_t id1, gid_t id2, int flags) -{ - /* See the comment for setuid above. */ - return 0; -} - static int selinux_task_setpgid(struct task_struct *p, pid_t pgid) { return current_has_perm(p, PROCESS__SETPGID); @@ -3372,12 +3308,6 @@ static void selinux_task_getsecid(struct task_struct *p, u32 *secid) *secid = task_sid(p); } -static int selinux_task_setgroups(struct group_info *group_info) -{ - /* See the comment for setuid above. */ - return 0; -} - static int selinux_task_setnice(struct task_struct *p, int nice) { int rc; @@ -3408,11 +3338,6 @@ static int selinux_task_getioprio(struct task_struct *p) static int selinux_task_setrlimit(unsigned int resource, struct rlimit *new_rlim) { struct rlimit *old_rlim = current->signal->rlim + resource; - int rc; - - rc = secondary_ops->task_setrlimit(resource, new_rlim); - if (rc) - return rc; /* Control the ability to change the hard limit (whether lowering or raising it), so that the hard limit can @@ -3451,10 +3376,6 @@ static int selinux_task_kill(struct task_struct *p, struct siginfo *info, u32 perm; int rc; - rc = secondary_ops->task_kill(p, info, sig, secid); - if (rc) - return rc; - if (!sig) perm = PROCESS__SIGNULL; /* null signal; existence test */ else @@ -3467,18 +3388,6 @@ static int selinux_task_kill(struct task_struct *p, struct siginfo *info, return rc; } -static int selinux_task_prctl(int option, - unsigned long arg2, - unsigned long arg3, - unsigned long arg4, - unsigned long arg5) -{ - /* The current prctl operations do not appear to require - any SELinux controls since they merely observe or modify - the state of the current process. */ - return secondary_ops->task_prctl(option, arg2, arg3, arg4, arg5); -} - static int selinux_task_wait(struct task_struct *p) { return task_has_perm(p, current, PROCESS__SIGCHLD); @@ -4047,10 +3956,6 @@ static int selinux_socket_unix_stream_connect(struct socket *sock, struct avc_audit_data ad; int err; - err = secondary_ops->unix_stream_connect(sock, other, newsk); - if (err) - return err; - isec = SOCK_INODE(sock)->i_security; other_isec = SOCK_INODE(other)->i_security; @@ -5167,11 +5072,6 @@ static int selinux_shm_shmat(struct shmid_kernel *shp, char __user *shmaddr, int shmflg) { u32 perms; - int rc; - - rc = secondary_ops->shm_shmat(shp, shmaddr, shmflg); - if (rc) - return rc; if (shmflg & SHM_RDONLY) perms = SHM__READ; @@ -5581,7 +5481,6 @@ static struct security_operations selinux_ops = { .netlink_recv = selinux_netlink_recv, .bprm_set_creds = selinux_bprm_set_creds, - .bprm_check_security = selinux_bprm_check_security, .bprm_committing_creds = selinux_bprm_committing_creds, .bprm_committed_creds = selinux_bprm_committed_creds, .bprm_secureexec = selinux_bprm_secureexec, @@ -5623,8 +5522,6 @@ static struct security_operations selinux_ops = { .inode_getsecurity = selinux_inode_getsecurity, .inode_setsecurity = selinux_inode_setsecurity, .inode_listsecurity = selinux_inode_listsecurity, - .inode_need_killpriv = selinux_inode_need_killpriv, - .inode_killpriv = selinux_inode_killpriv, .inode_getsecid = selinux_inode_getsecid, .file_permission = selinux_file_permission, @@ -5644,17 +5541,12 @@ static struct security_operations selinux_ops = { .task_create = selinux_task_create, .cred_free = selinux_cred_free, .cred_prepare = selinux_cred_prepare, - .cred_commit = selinux_cred_commit, .kernel_act_as = selinux_kernel_act_as, .kernel_create_files_as = selinux_kernel_create_files_as, - .task_setuid = selinux_task_setuid, - .task_fix_setuid = selinux_task_fix_setuid, - .task_setgid = selinux_task_setgid, .task_setpgid = selinux_task_setpgid, .task_getpgid = selinux_task_getpgid, .task_getsid = selinux_task_getsid, .task_getsecid = selinux_task_getsecid, - .task_setgroups = selinux_task_setgroups, .task_setnice = selinux_task_setnice, .task_setioprio = selinux_task_setioprio, .task_getioprio = selinux_task_getioprio, @@ -5664,7 +5556,6 @@ static struct security_operations selinux_ops = { .task_movememory = selinux_task_movememory, .task_kill = selinux_task_kill, .task_wait = selinux_task_wait, - .task_prctl = selinux_task_prctl, .task_to_inode = selinux_task_to_inode, .ipc_permission = selinux_ipc_permission, diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 3cc45168f674..c4e062336ef3 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -60,9 +60,7 @@ struct superblock_security_struct { u32 def_sid; /* default SID for labeling */ u32 mntpoint_sid; /* SECURITY_FS_USE_MNTPOINT context for files */ unsigned int behavior; /* labeling behavior */ - unsigned char initialized; /* initialization flag */ unsigned char flags; /* which mount options were specified */ - unsigned char proc; /* proc fs */ struct mutex lock; struct list_head isec_head; spinlock_t isec_lock; diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h index 72447370bc95..e1d9db779983 100644 --- a/security/selinux/include/security.h +++ b/security/selinux/include/security.h @@ -37,15 +37,23 @@ #define POLICYDB_VERSION_MAX POLICYDB_VERSION_BOUNDARY #endif +/* Mask for just the mount related flags */ +#define SE_MNTMASK 0x0f +/* Super block security struct flags for mount options */ #define CONTEXT_MNT 0x01 #define FSCONTEXT_MNT 0x02 #define ROOTCONTEXT_MNT 0x04 #define DEFCONTEXT_MNT 0x08 +/* Non-mount related flags */ +#define SE_SBINITIALIZED 0x10 +#define SE_SBPROC 0x20 +#define SE_SBLABELSUPP 0x40 #define CONTEXT_STR "context=" #define FSCONTEXT_STR "fscontext=" #define ROOTCONTEXT_STR "rootcontext=" #define DEFCONTEXT_STR "defcontext=" +#define LABELSUPP_STR "seclabel" struct netlbl_lsm_secattr; |