diff options
Diffstat (limited to 'security')
37 files changed, 924 insertions, 471 deletions
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 7b0e13ce7dc7..f919ebd042fd 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -577,7 +577,7 @@ static struct aa_label *x_to_label(struct aa_profile *profile, stack = NULL; break; } - /* fall through - to X_NAME */ + fallthrough; /* to X_NAME */ case AA_X_NAME: if (xindex & AA_X_CHILD) /* released by caller */ diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 30c246a9d440..fa49b81eb54c 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -292,13 +292,13 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms) switch (AUDIT_MODE(profile)) { case AUDIT_ALL: perms->audit = ALL_PERMS_MASK; - /* fall through */ + fallthrough; case AUDIT_NOQUIET: perms->quiet = 0; break; case AUDIT_QUIET: perms->audit = 0; - /* fall through */ + fallthrough; case AUDIT_QUIET_DENIED: perms->quiet = ALL_PERMS_MASK; break; diff --git a/security/bpf/hooks.c b/security/bpf/hooks.c index 32d32d485451..788667d582ae 100644 --- a/security/bpf/hooks.c +++ b/security/bpf/hooks.c @@ -11,6 +11,7 @@ static struct security_hook_list bpf_lsm_hooks[] __lsm_ro_after_init = { LSM_HOOK_INIT(NAME, bpf_lsm_##NAME), #include <linux/lsm_hook_defs.h> #undef LSM_HOOK + LSM_HOOK_INIT(inode_free_security, bpf_inode_storage_free), }; static int __init bpf_lsm_init(void) @@ -20,7 +21,12 @@ static int __init bpf_lsm_init(void) return 0; } +struct lsm_blob_sizes bpf_lsm_blob_sizes __lsm_ro_after_init = { + .lbs_inode = sizeof(struct bpf_storage_blob), +}; + DEFINE_LSM(bpf) = { .name = "bpf", .init = bpf_lsm_init, + .blobs = &bpf_lsm_blob_sizes }; diff --git a/security/device_cgroup.c b/security/device_cgroup.c index 43ab0ad45c1b..04375df52fc9 100644 --- a/security/device_cgroup.c +++ b/security/device_cgroup.c @@ -354,7 +354,8 @@ static bool match_exception_partial(struct list_head *exceptions, short type, { struct dev_exception_item *ex; - list_for_each_entry_rcu(ex, exceptions, list) { + list_for_each_entry_rcu(ex, exceptions, list, + lockdep_is_held(&devcgroup_mutex)) { if ((type & DEVCG_DEV_BLOCK) && !(ex->type & DEVCG_DEV_BLOCK)) continue; if ((type & DEVCG_DEV_CHAR) && !(ex->type & DEVCG_DEV_CHAR)) diff --git a/security/integrity/digsig.c b/security/integrity/digsig.c index e9cbadade74b..0f518dcfde05 100644 --- a/security/integrity/digsig.c +++ b/security/integrity/digsig.c @@ -10,6 +10,7 @@ #include <linux/sched.h> #include <linux/slab.h> #include <linux/cred.h> +#include <linux/kernel_read_file.h> #include <linux/key-type.h> #include <linux/digsig.h> #include <linux/vmalloc.h> @@ -169,17 +170,18 @@ int __init integrity_add_key(const unsigned int id, const void *data, int __init integrity_load_x509(const unsigned int id, const char *path) { - void *data; - loff_t size; + void *data = NULL; + size_t size; int rc; key_perm_t perm; - rc = kernel_read_file_from_path(path, &data, &size, 0, + rc = kernel_read_file_from_path(path, 0, &data, INT_MAX, NULL, READING_X509_CERTIFICATE); if (rc < 0) { pr_err("Unable to open file: %s (%d)", path, rc); return rc; } + size = rc; perm = (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_VIEW | KEY_USR_READ; diff --git a/security/integrity/digsig_asymmetric.c b/security/integrity/digsig_asymmetric.c index cfa4127d0518..a662024b4c70 100644 --- a/security/integrity/digsig_asymmetric.c +++ b/security/integrity/digsig_asymmetric.c @@ -55,8 +55,14 @@ static struct key *request_asymmetric_key(struct key *keyring, uint32_t keyid) } if (IS_ERR(key)) { - pr_err_ratelimited("Request for unknown key '%s' err %ld\n", - name, PTR_ERR(key)); + if (keyring) + pr_err_ratelimited("Request for unknown key '%s' in '%s' keyring. err %ld\n", + name, keyring->description, + PTR_ERR(key)); + else + pr_err_ratelimited("Request for unknown key '%s' err %ld\n", + name, PTR_ERR(key)); + switch (PTR_ERR(key)) { /* Hide some search errors */ case -EACCES: @@ -99,14 +105,22 @@ int asymmetric_verify(struct key *keyring, const char *sig, memset(&pks, 0, sizeof(pks)); pks.hash_algo = hash_algo_name[hdr->hash_algo]; - if (hdr->hash_algo == HASH_ALGO_STREEBOG_256 || - hdr->hash_algo == HASH_ALGO_STREEBOG_512) { + switch (hdr->hash_algo) { + case HASH_ALGO_STREEBOG_256: + case HASH_ALGO_STREEBOG_512: /* EC-RDSA and Streebog should go together. */ pks.pkey_algo = "ecrdsa"; pks.encoding = "raw"; - } else { + break; + case HASH_ALGO_SM3_256: + /* SM2 and SM3 should go together. */ + pks.pkey_algo = "sm2"; + pks.encoding = "raw"; + break; + default: pks.pkey_algo = "rsa"; pks.encoding = "pkcs1"; + break; } pks.digest = (u8 *)data; pks.digest_size = datalen; diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 0d36259b690d..76d19146d74b 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -59,6 +59,9 @@ static int __init evm_set_fixmode(char *str) { if (strncmp(str, "fix", 3) == 0) evm_fixmode = 1; + else + pr_err("invalid \"%s\" mode", str); + return 0; } __setup("evm=", evm_set_fixmode); @@ -181,6 +184,12 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, break; case EVM_IMA_XATTR_DIGSIG: case EVM_XATTR_PORTABLE_DIGSIG: + /* accept xattr with non-empty signature field */ + if (xattr_len <= sizeof(struct signature_v2_hdr)) { + evm_status = INTEGRITY_FAIL; + goto out; + } + hdr = (struct signature_v2_hdr *)xattr_data; digest.hdr.algo = hdr->hash_algo; rc = evm_calc_hash(dentry, xattr_name, xattr_value, diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 38043074ce5e..6ebefec616e4 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -101,7 +101,7 @@ struct ima_template_entry { struct tpm_digest *digests; struct ima_template_desc *template_desc; /* template descriptor */ u32 template_data_len; - struct ima_field_data template_data[0]; /* template related data */ + struct ima_field_data template_data[]; /* template related data */ }; struct ima_queue_entry { diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 372d16382960..3dd8c2e4314e 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -19,18 +19,29 @@ static int __init default_appraise_setup(char *str) { #ifdef CONFIG_IMA_APPRAISE_BOOTPARAM - if (arch_ima_get_secureboot()) { - pr_info("Secure boot enabled: ignoring ima_appraise=%s boot parameter option", - str); - return 1; - } + bool sb_state = arch_ima_get_secureboot(); + int appraisal_state = ima_appraise; if (strncmp(str, "off", 3) == 0) - ima_appraise = 0; + appraisal_state = 0; else if (strncmp(str, "log", 3) == 0) - ima_appraise = IMA_APPRAISE_LOG; + appraisal_state = IMA_APPRAISE_LOG; else if (strncmp(str, "fix", 3) == 0) - ima_appraise = IMA_APPRAISE_FIX; + appraisal_state = IMA_APPRAISE_FIX; + else if (strncmp(str, "enforce", 7) == 0) + appraisal_state = IMA_APPRAISE_ENFORCE; + else + pr_err("invalid \"%s\" appraise option", str); + + /* If appraisal state was changed, but secure boot is enabled, + * keep its default */ + if (sb_state) { + if (!(appraisal_state & IMA_APPRAISE_ENFORCE)) + pr_info("Secure boot enabled: ignoring ima_appraise=%s option", + str); + } else { + ima_appraise = appraisal_state; + } #endif return 1; } @@ -223,7 +234,7 @@ static int xattr_verify(enum ima_hooks func, struct integrity_iint_cache *iint, case IMA_XATTR_DIGEST_NG: /* first byte contains algorithm id */ hash_start = 1; - /* fall through */ + fallthrough; case IMA_XATTR_DIGEST: if (iint->flags & IMA_DIGSIG_REQUIRED) { *cause = "IMA-signature-required"; @@ -395,7 +406,7 @@ int ima_appraise_measurement(enum ima_hooks func, /* It's fine not to have xattrs when using a modsig. */ if (try_modsig) break; - /* fall through */ + fallthrough; case INTEGRITY_NOLABEL: /* No security.evm xattr. */ cause = "missing-HMAC"; goto out; diff --git a/security/integrity/ima/ima_crypto.c b/security/integrity/ima/ima_crypto.c index 011c3c76af86..21989fa0c107 100644 --- a/security/integrity/ima/ima_crypto.c +++ b/security/integrity/ima/ima_crypto.c @@ -829,6 +829,8 @@ static int ima_calc_boot_aggregate_tfm(char *digest, u16 alg_id, /* now accumulate with current aggregate */ rc = crypto_shash_update(shash, d.digest, crypto_shash_digestsize(tfm)); + if (rc != 0) + return rc; } /* * Extend cumulative digest over TPM registers 8-9, which contain diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index e3fcad871861..ea8ff8a07b36 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -13,6 +13,7 @@ */ #include <linux/fcntl.h> +#include <linux/kernel_read_file.h> #include <linux/slab.h> #include <linux/init.h> #include <linux/seq_file.h> @@ -272,9 +273,9 @@ static const struct file_operations ima_ascii_measurements_ops = { static ssize_t ima_read_policy(char *path) { - void *data; + void *data = NULL; char *datap; - loff_t size; + size_t size; int rc, pathlen = strlen(path); char *p; @@ -283,11 +284,14 @@ static ssize_t ima_read_policy(char *path) datap = path; strsep(&datap, "\n"); - rc = kernel_read_file_from_path(path, &data, &size, 0, READING_POLICY); + rc = kernel_read_file_from_path(path, 0, &data, INT_MAX, NULL, + READING_POLICY); if (rc < 0) { pr_err("Unable to open file: %s (%d)", path, rc); return rc; } + size = rc; + rc = 0; datap = data; while (size > 0 && (p = strsep(&datap, "\n"))) { diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 8a91711ca79b..2d1af8899cab 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -18,6 +18,7 @@ #include <linux/module.h> #include <linux/file.h> #include <linux/binfmts.h> +#include <linux/kernel_read_file.h> #include <linux/mount.h> #include <linux/mman.h> #include <linux/slab.h> @@ -50,18 +51,23 @@ static int __init hash_setup(char *str) return 1; if (strcmp(template_desc->name, IMA_TEMPLATE_IMA_NAME) == 0) { - if (strncmp(str, "sha1", 4) == 0) + if (strncmp(str, "sha1", 4) == 0) { ima_hash_algo = HASH_ALGO_SHA1; - else if (strncmp(str, "md5", 3) == 0) + } else if (strncmp(str, "md5", 3) == 0) { ima_hash_algo = HASH_ALGO_MD5; - else + } else { + pr_err("invalid hash algorithm \"%s\" for template \"%s\"", + str, IMA_TEMPLATE_IMA_NAME); return 1; + } goto out; } i = match_string(hash_algo_name, HASH_ALGO__LAST, str); - if (i < 0) + if (i < 0) { + pr_err("invalid hash algorithm \"%s\"", str); return 1; + } ima_hash_algo = i; out: @@ -531,6 +537,16 @@ int ima_file_hash(struct file *file, char *buf, size_t buf_size) return -EOPNOTSUPP; mutex_lock(&iint->mutex); + + /* + * ima_file_hash can be called when ima_collect_measurement has still + * not been called, we might not always have a hash. + */ + if (!iint->ima_hash) { + mutex_unlock(&iint->mutex); + return -EOPNOTSUPP; + } + if (buf) { size_t copied_size; @@ -601,6 +617,7 @@ void ima_post_path_mknod(struct dentry *dentry) * ima_read_file - pre-measure/appraise hook decision based on policy * @file: pointer to the file to be measured/appraised/audit * @read_id: caller identifier + * @contents: whether a subsequent call will be made to ima_post_read_file() * * Permit reading a file based on policy. The policy rules are written * in terms of the policy identifier. Appraising the integrity of @@ -608,22 +625,37 @@ void ima_post_path_mknod(struct dentry *dentry) * * For permission return 0, otherwise return -EACCES. */ -int ima_read_file(struct file *file, enum kernel_read_file_id read_id) +int ima_read_file(struct file *file, enum kernel_read_file_id read_id, + bool contents) { + enum ima_hooks func; + u32 secid; + /* - * READING_FIRMWARE_PREALLOC_BUFFER - * * Do devices using pre-allocated memory run the risk of the * firmware being accessible to the device prior to the completion * of IMA's signature verification any more than when using two - * buffers? + * buffers? It may be desirable to include the buffer address + * in this API and walk all the dma_map_single() mappings to check. */ - return 0; + + /* + * There will be a call made to ima_post_read_file() with + * a filled buffer, so we don't need to perform an extra + * read early here. + */ + if (contents) + return 0; + + /* Read entire file for all partial reads. */ + func = read_idmap[read_id] ?: FILE_CHECK; + security_task_getsecid(current, &secid); + return process_measurement(file, current_cred(), secid, NULL, + 0, MAY_READ, func); } const int read_idmap[READING_MAX_ID] = { [READING_FIRMWARE] = FIRMWARE_CHECK, - [READING_FIRMWARE_PREALLOC_BUFFER] = FIRMWARE_CHECK, [READING_MODULE] = MODULE_CHECK, [READING_KEXEC_IMAGE] = KEXEC_KERNEL_CHECK, [READING_KEXEC_INITRAMFS] = KEXEC_INITRAMFS_CHECK, @@ -649,15 +681,6 @@ int ima_post_read_file(struct file *file, void *buf, loff_t size, enum ima_hooks func; u32 secid; - if (!file && read_id == READING_FIRMWARE) { - if ((ima_appraise & IMA_APPRAISE_FIRMWARE) && - (ima_appraise & IMA_APPRAISE_ENFORCE)) { - pr_err("Prevent firmware loading_store.\n"); - return -EACCES; /* INTEGRITY_UNKNOWN */ - } - return 0; - } - /* permit signed certs */ if (!file && read_id == READING_X509_CERTIFICATE) return 0; @@ -677,6 +700,8 @@ int ima_post_read_file(struct file *file, void *buf, loff_t size, /** * ima_load_data - appraise decision based on policy * @id: kernel load data caller identifier + * @contents: whether the full contents will be available in a later + * call to ima_post_load_data(). * * Callers of this LSM hook can not measure, appraise, or audit the * data provided by userspace. Enforce policy rules requring a file @@ -684,7 +709,7 @@ int ima_post_read_file(struct file *file, void *buf, loff_t size, * * For permission return 0, otherwise return -EACCES. */ -int ima_load_data(enum kernel_load_data_id id) +int ima_load_data(enum kernel_load_data_id id, bool contents) { bool ima_enforce, sig_enforce; @@ -705,7 +730,7 @@ int ima_load_data(enum kernel_load_data_id id) } break; case LOADING_FIRMWARE: - if (ima_enforce && (ima_appraise & IMA_APPRAISE_FIRMWARE)) { + if (ima_enforce && (ima_appraise & IMA_APPRAISE_FIRMWARE) && !contents) { pr_err("Prevent firmware sysfs fallback loading.\n"); return -EACCES; /* INTEGRITY_UNKNOWN */ } @@ -724,6 +749,35 @@ int ima_load_data(enum kernel_load_data_id id) return 0; } +/** + * ima_post_load_data - appraise decision based on policy + * @buf: pointer to in memory file contents + * @size: size of in memory file contents + * @id: kernel load data caller identifier + * @description: @id-specific description of contents + * + * Measure/appraise/audit in memory buffer based on policy. Policy rules + * are written in terms of a policy identifier. + * + * On success return 0. On integrity appraisal error, assuming the file + * is in policy and IMA-appraisal is in enforcing mode, return -EACCES. + */ +int ima_post_load_data(char *buf, loff_t size, + enum kernel_load_data_id load_id, + char *description) +{ + if (load_id == LOADING_FIRMWARE) { + if ((ima_appraise & IMA_APPRAISE_FIRMWARE) && + (ima_appraise & IMA_APPRAISE_ENFORCE)) { + pr_err("Prevent firmware loading_store.\n"); + return -EACCES; /* INTEGRITY_UNKNOWN */ + } + return 0; + } + + return 0; +} + /* * process_buffer_measurement - Measure the buffer to ima log. * @inode: inode associated with the object being measured (NULL for KEY_CHECK) diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 07f033634b27..9b5adeaa47fc 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -9,6 +9,7 @@ #include <linux/init.h> #include <linux/list.h> +#include <linux/kernel_read_file.h> #include <linux/fs.h> #include <linux/security.h> #include <linux/magic.h> @@ -59,6 +60,11 @@ enum policy_types { ORIGINAL_TCB = 1, DEFAULT_TCB }; enum policy_rule_list { IMA_DEFAULT_POLICY = 1, IMA_CUSTOM_POLICY }; +struct ima_rule_opt_list { + size_t count; + char *items[]; +}; + struct ima_rule_entry { struct list_head list; int action; @@ -78,7 +84,7 @@ struct ima_rule_entry { int type; /* audit type */ } lsm[MAX_LSM_RULES]; char *fsname; - char *keyrings; /* Measure keys added to these keyrings */ + struct ima_rule_opt_list *keyrings; /* Measure keys added to these keyrings */ struct ima_template_desc *template; }; @@ -206,10 +212,6 @@ static LIST_HEAD(ima_policy_rules); static LIST_HEAD(ima_temp_rules); static struct list_head *ima_rules = &ima_default_rules; -/* Pre-allocated buffer used for matching keyrings. */ -static char *ima_keyrings; -static size_t ima_keyrings_len; - static int ima_policy __initdata; static int __init default_measure_policy_setup(char *str) @@ -240,6 +242,8 @@ static int __init policy_setup(char *str) ima_use_secure_boot = true; else if (strcmp(p, "fail_securely") == 0) ima_fail_unverifiable_sigs = true; + else + pr_err("policy \"%s\" not found", p); } return 1; @@ -253,6 +257,72 @@ static int __init default_appraise_policy_setup(char *str) } __setup("ima_appraise_tcb", default_appraise_policy_setup); +static struct ima_rule_opt_list *ima_alloc_rule_opt_list(const substring_t *src) +{ + struct ima_rule_opt_list *opt_list; + size_t count = 0; + char *src_copy; + char *cur, *next; + size_t i; + + src_copy = match_strdup(src); + if (!src_copy) + return ERR_PTR(-ENOMEM); + + next = src_copy; + while ((cur = strsep(&next, "|"))) { + /* Don't accept an empty list item */ + if (!(*cur)) { + kfree(src_copy); + return ERR_PTR(-EINVAL); + } + count++; + } + + /* Don't accept an empty list */ + if (!count) { + kfree(src_copy); + return ERR_PTR(-EINVAL); + } + + opt_list = kzalloc(struct_size(opt_list, items, count), GFP_KERNEL); + if (!opt_list) { + kfree(src_copy); + return ERR_PTR(-ENOMEM); + } + + /* + * strsep() has already replaced all instances of '|' with '\0', + * leaving a byte sequence of NUL-terminated strings. Reference each + * string with the array of items. + * + * IMPORTANT: Ownership of the allocated buffer is transferred from + * src_copy to the first element in the items array. To free the + * buffer, kfree() must only be called on the first element of the + * array. + */ + for (i = 0, cur = src_copy; i < count; i++) { + opt_list->items[i] = cur; + cur = strchr(cur, '\0') + 1; + } + opt_list->count = count; + + return opt_list; +} + +static void ima_free_rule_opt_list(struct ima_rule_opt_list *opt_list) +{ + if (!opt_list) + return; + + if (opt_list->count) { + kfree(opt_list->items[0]); + opt_list->count = 0; + } + + kfree(opt_list); +} + static void ima_lsm_free_rule(struct ima_rule_entry *entry) { int i; @@ -274,7 +344,7 @@ static void ima_free_rule(struct ima_rule_entry *entry) * the defined_templates list and cannot be freed here */ kfree(entry->fsname); - kfree(entry->keyrings); + ima_free_rule_opt_list(entry->keyrings); ima_lsm_free_rule(entry); kfree(entry); } @@ -284,15 +354,14 @@ static struct ima_rule_entry *ima_lsm_copy_rule(struct ima_rule_entry *entry) struct ima_rule_entry *nentry; int i; - nentry = kmalloc(sizeof(*nentry), GFP_KERNEL); - if (!nentry) - return NULL; - /* * Immutable elements are copied over as pointers and data; only * lsm rules can change */ - memcpy(nentry, entry, sizeof(*nentry)); + nentry = kmemdup(entry, sizeof(*nentry), GFP_KERNEL); + if (!nentry) + return NULL; + memset(nentry->lsm, 0, sizeof_field(struct ima_rule_entry, lsm)); for (i = 0; i < MAX_LSM_RULES; i++) { @@ -394,8 +463,8 @@ int ima_lsm_policy_change(struct notifier_block *nb, unsigned long event, static bool ima_match_keyring(struct ima_rule_entry *rule, const char *keyring, const struct cred *cred) { - char *next_keyring, *keyrings_ptr; bool matched = false; + size_t i; if ((rule->flags & IMA_UID) && !rule->uid_op(cred->uid, rule->uid)) return false; @@ -406,15 +475,8 @@ static bool ima_match_keyring(struct ima_rule_entry *rule, if (!keyring) return false; - strcpy(ima_keyrings, rule->keyrings); - - /* - * "keyrings=" is specified in the policy in the format below: - * keyrings=.builtin_trusted_keys|.ima|.evm - */ - keyrings_ptr = ima_keyrings; - while ((next_keyring = strsep(&keyrings_ptr, "|")) != NULL) { - if (!strcmp(next_keyring, keyring)) { + for (i = 0; i < rule->keyrings->count; i++) { + if (!strcmp(rule->keyrings->items[i], keyring)) { matched = true; break; } @@ -1065,7 +1127,6 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) bool uid_token; struct ima_template_desc *template_desc; int result = 0; - size_t keyrings_len; ab = integrity_audit_log_start(audit_context(), GFP_KERNEL, AUDIT_INTEGRITY_POLICY_RULE); @@ -1174,7 +1235,8 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) entry->func = POLICY_CHECK; else if (strcmp(args[0].from, "KEXEC_CMDLINE") == 0) entry->func = KEXEC_CMDLINE; - else if (strcmp(args[0].from, "KEY_CHECK") == 0) + else if (IS_ENABLED(CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS) && + strcmp(args[0].from, "KEY_CHECK") == 0) entry->func = KEY_CHECK; else result = -EINVAL; @@ -1231,37 +1293,19 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) case Opt_keyrings: ima_log_string(ab, "keyrings", args[0].from); - keyrings_len = strlen(args[0].from) + 1; - - if ((entry->keyrings) || - (keyrings_len < 2)) { + if (!IS_ENABLED(CONFIG_IMA_MEASURE_ASYMMETRIC_KEYS) || + entry->keyrings) { result = -EINVAL; break; } - if (keyrings_len > ima_keyrings_len) { - char *tmpbuf; - - tmpbuf = krealloc(ima_keyrings, keyrings_len, - GFP_KERNEL); - if (!tmpbuf) { - result = -ENOMEM; - break; - } - - ima_keyrings = tmpbuf; - ima_keyrings_len = keyrings_len; - } - - entry->keyrings = kstrdup(args[0].from, GFP_KERNEL); - if (!entry->keyrings) { - kfree(ima_keyrings); - ima_keyrings = NULL; - ima_keyrings_len = 0; - result = -ENOMEM; + entry->keyrings = ima_alloc_rule_opt_list(args); + if (IS_ERR(entry->keyrings)) { + result = PTR_ERR(entry->keyrings); + entry->keyrings = NULL; break; } - result = 0; + entry->flags |= IMA_KEYRINGS; break; case Opt_fsuuid: @@ -1279,12 +1323,12 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) case Opt_uid_gt: case Opt_euid_gt: entry->uid_op = &uid_gt; - /* fall through */ + fallthrough; case Opt_uid_lt: case Opt_euid_lt: if ((token == Opt_uid_lt) || (token == Opt_euid_lt)) entry->uid_op = &uid_lt; - /* fall through */ + fallthrough; case Opt_uid_eq: case Opt_euid_eq: uid_token = (token == Opt_uid_eq) || @@ -1313,11 +1357,11 @@ static int ima_parse_rule(char *rule, struct ima_rule_entry *entry) break; case Opt_fowner_gt: entry->fowner_op = &uid_gt; - /* fall through */ + fallthrough; case Opt_fowner_lt: if (token == Opt_fowner_lt) entry->fowner_op = &uid_lt; - /* fall through */ + fallthrough; case Opt_fowner_eq: ima_log_string_op(ab, "fowner", args[0].from, entry->fowner_op); @@ -1574,6 +1618,15 @@ static void policy_func_show(struct seq_file *m, enum ima_hooks func) seq_printf(m, "func=%d ", func); } +static void ima_show_rule_opt_list(struct seq_file *m, + const struct ima_rule_opt_list *opt_list) +{ + size_t i; + + for (i = 0; i < opt_list->count; i++) + seq_printf(m, "%s%s", i ? "|" : "", opt_list->items[i]); +} + int ima_policy_show(struct seq_file *m, void *v) { struct ima_rule_entry *entry = v; @@ -1630,9 +1683,8 @@ int ima_policy_show(struct seq_file *m, void *v) } if (entry->flags & IMA_KEYRINGS) { - if (entry->keyrings != NULL) - snprintf(tbuf, sizeof(tbuf), "%s", entry->keyrings); - seq_printf(m, pt(Opt_keyrings), tbuf); + seq_puts(m, "keyrings="); + ima_show_rule_opt_list(m, entry->keyrings); seq_puts(m, " "); } diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index fb4ec270f620..c096ef8945c7 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -133,7 +133,7 @@ unsigned long ima_get_binary_runtime_size(void) return ULONG_MAX; else return binary_runtime_size + sizeof(struct ima_kexec_hdr); -}; +} static int ima_pcr_extend(struct tpm_digest *digests_arg, int pcr) { diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 41a5f435b793..c022ee9e2a4e 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -77,7 +77,7 @@ static void ima_show_template_data_ascii(struct seq_file *m, /* skip ':' and '\0' */ buf_ptr += 2; buflen -= buf_ptr - field_data->data; - /* fall through */ + fallthrough; case DATA_FMT_DIGEST: case DATA_FMT_HEX: if (!buflen) diff --git a/security/integrity/integrity_audit.c b/security/integrity/integrity_audit.c index f25e7df099c8..29220056207f 100644 --- a/security/integrity/integrity_audit.c +++ b/security/integrity/integrity_audit.c @@ -47,7 +47,7 @@ void integrity_audit_message(int audit_msgno, struct inode *inode, ab = audit_log_start(audit_context(), GFP_KERNEL, audit_msgno); audit_log_format(ab, "pid=%d uid=%u auid=%u ses=%u", task_pid_nr(current), - from_kuid(&init_user_ns, current_cred()->uid), + from_kuid(&init_user_ns, current_uid()), from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); audit_log_task_context(ab); diff --git a/security/integrity/platform_certs/load_uefi.c b/security/integrity/platform_certs/load_uefi.c index 253fb9a7fc98..ee4b4c666854 100644 --- a/security/integrity/platform_certs/load_uefi.c +++ b/security/integrity/platform_certs/load_uefi.c @@ -66,6 +66,65 @@ static __init void *get_cert_list(efi_char16_t *name, efi_guid_t *guid, } /* + * load_moklist_certs() - Load MokList certs + * + * Load the certs contained in the UEFI MokListRT database into the + * platform trusted keyring. + * + * This routine checks the EFI MOK config table first. If and only if + * that fails, this routine uses the MokListRT ordinary UEFI variable. + * + * Return: Status + */ +static int __init load_moklist_certs(void) +{ + struct efi_mokvar_table_entry *mokvar_entry; + efi_guid_t mok_var = EFI_SHIM_LOCK_GUID; + void *mok; + unsigned long moksize; + efi_status_t status; + int rc; + + /* First try to load certs from the EFI MOKvar config table. + * It's not an error if the MOKvar config table doesn't exist + * or the MokListRT entry is not found in it. + */ + mokvar_entry = efi_mokvar_entry_find("MokListRT"); + if (mokvar_entry) { + rc = parse_efi_signature_list("UEFI:MokListRT (MOKvar table)", + mokvar_entry->data, + mokvar_entry->data_size, + get_handler_for_db); + /* All done if that worked. */ + if (!rc) + return rc; + + pr_err("Couldn't parse MokListRT signatures from EFI MOKvar config table: %d\n", + rc); + } + + /* Get MokListRT. It might not exist, so it isn't an error + * if we can't get it. + */ + mok = get_cert_list(L"MokListRT", &mok_var, &moksize, &status); + if (mok) { + rc = parse_efi_signature_list("UEFI:MokListRT", + mok, moksize, get_handler_for_db); + kfree(mok); + if (rc) + pr_err("Couldn't parse MokListRT signatures: %d\n", rc); + return rc; + } + if (status == EFI_NOT_FOUND) + pr_debug("MokListRT variable wasn't found\n"); + else + pr_info("Couldn't get UEFI MokListRT\n"); + return 0; +} + +/* + * load_uefi_certs() - Load certs from UEFI sources + * * Load the certs contained in the UEFI databases into the platform trusted * keyring and the UEFI blacklisted X.509 cert SHA256 hashes into the blacklist * keyring. @@ -73,17 +132,16 @@ static __init void *get_cert_list(efi_char16_t *name, efi_guid_t *guid, static int __init load_uefi_certs(void) { efi_guid_t secure_var = EFI_IMAGE_SECURITY_DATABASE_GUID; - efi_guid_t mok_var = EFI_SHIM_LOCK_GUID; - void *db = NULL, *dbx = NULL, *mok = NULL; - unsigned long dbsize = 0, dbxsize = 0, moksize = 0; + void *db = NULL, *dbx = NULL; + unsigned long dbsize = 0, dbxsize = 0; efi_status_t status; int rc = 0; if (!efi_rt_services_supported(EFI_RT_SUPPORTED_GET_VARIABLE)) return false; - /* Get db, MokListRT, and dbx. They might not exist, so it isn't - * an error if we can't get them. + /* Get db and dbx. They might not exist, so it isn't an error + * if we can't get them. */ if (!uefi_check_ignore_db()) { db = get_cert_list(L"db", &secure_var, &dbsize, &status); @@ -102,20 +160,6 @@ static int __init load_uefi_certs(void) } } - mok = get_cert_list(L"MokListRT", &mok_var, &moksize, &status); - if (!mok) { - if (status == EFI_NOT_FOUND) - pr_debug("MokListRT variable wasn't found\n"); - else - pr_info("Couldn't get UEFI MokListRT\n"); - } else { - rc = parse_efi_signature_list("UEFI:MokListRT", - mok, moksize, get_handler_for_db); - if (rc) - pr_err("Couldn't parse MokListRT signatures: %d\n", rc); - kfree(mok); - } - dbx = get_cert_list(L"dbx", &secure_var, &dbxsize, &status); if (!dbx) { if (status == EFI_NOT_FOUND) @@ -131,6 +175,9 @@ static int __init load_uefi_certs(void) kfree(dbx); } + /* Load the MokListRT certs */ + rc = load_moklist_certs(); + return rc; } late_initcall(load_uefi_certs); diff --git a/security/keys/compat.c b/security/keys/compat.c index 6ee9d8f6a4a5..1545efdca562 100644 --- a/security/keys/compat.c +++ b/security/keys/compat.c @@ -12,39 +12,6 @@ #include "internal.h" /* - * Instantiate a key with the specified compatibility multipart payload and - * link the key into the destination keyring if one is given. - * - * The caller must have the appropriate instantiation permit set for this to - * work (see keyctl_assume_authority). No other permissions are required. - * - * If successful, 0 will be returned. - */ -static long compat_keyctl_instantiate_key_iov( - key_serial_t id, - const struct compat_iovec __user *_payload_iov, - unsigned ioc, - key_serial_t ringid) -{ - struct iovec iovstack[UIO_FASTIOV], *iov = iovstack; - struct iov_iter from; - long ret; - - if (!_payload_iov) - ioc = 0; - - ret = compat_import_iovec(WRITE, _payload_iov, ioc, - ARRAY_SIZE(iovstack), &iov, - &from); - if (ret < 0) - return ret; - - ret = keyctl_instantiate_key_common(id, &from, ringid); - kfree(iov); - return ret; -} - -/* * The key control system call, 32-bit compatibility version for 64-bit archs */ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option, @@ -114,8 +81,8 @@ COMPAT_SYSCALL_DEFINE5(keyctl, u32, option, return keyctl_reject_key(arg2, arg3, arg4, arg5); case KEYCTL_INSTANTIATE_IOV: - return compat_keyctl_instantiate_key_iov( - arg2, compat_ptr(arg3), arg4, arg5); + return keyctl_instantiate_key_iov(arg2, compat_ptr(arg3), arg4, + arg5); case KEYCTL_INVALIDATE: return keyctl_invalidate_key(arg2); diff --git a/security/keys/internal.h b/security/keys/internal.h index 338a526cbfa5..9b9cf3b6fcbb 100644 --- a/security/keys/internal.h +++ b/security/keys/internal.h @@ -262,11 +262,6 @@ extern long keyctl_instantiate_key_iov(key_serial_t, const struct iovec __user *, unsigned, key_serial_t); extern long keyctl_invalidate_key(key_serial_t); - -struct iov_iter; -extern long keyctl_instantiate_key_common(key_serial_t, - struct iov_iter *, - key_serial_t); extern long keyctl_restrict_keyring(key_serial_t id, const char __user *_type, const char __user *_restriction); diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index 9febd37a168f..61a614c21b9b 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -1164,7 +1164,7 @@ static int keyctl_change_reqkey_auth(struct key *key) * * If successful, 0 will be returned. */ -long keyctl_instantiate_key_common(key_serial_t id, +static long keyctl_instantiate_key_common(key_serial_t id, struct iov_iter *from, key_serial_t ringid) { @@ -1693,7 +1693,7 @@ long keyctl_session_to_parent(void) /* the replacement session keyring is applied just prior to userspace * restarting */ - ret = task_work_add(parent, newwork, true); + ret = task_work_add(parent, newwork, TWA_RESUME); if (!ret) newwork = NULL; unlock: diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index 7e0232db1707..1fe8b934f656 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -465,7 +465,7 @@ key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx) case -EAGAIN: /* no key */ if (ret) break; - /* fall through */ + fallthrough; case -ENOKEY: /* negative key */ ret = key_ref; break; @@ -487,7 +487,7 @@ key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx) case -EAGAIN: /* no key */ if (ret) break; - /* fall through */ + fallthrough; case -ENOKEY: /* negative key */ ret = key_ref; break; @@ -509,7 +509,7 @@ key_ref_t search_cred_keyrings_rcu(struct keyring_search_context *ctx) case -EAGAIN: /* no key */ if (ret) break; - /* fall through */ + fallthrough; case -ENOKEY: /* negative key */ ret = key_ref; break; diff --git a/security/keys/request_key.c b/security/keys/request_key.c index e1b9f1a80676..2da4404276f0 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -295,26 +295,26 @@ static int construct_get_dest_keyring(struct key **_dest_keyring) } } - /* fall through */ + fallthrough; case KEY_REQKEY_DEFL_THREAD_KEYRING: dest_keyring = key_get(cred->thread_keyring); if (dest_keyring) break; - /* fall through */ + fallthrough; case KEY_REQKEY_DEFL_PROCESS_KEYRING: dest_keyring = key_get(cred->process_keyring); if (dest_keyring) break; - /* fall through */ + fallthrough; case KEY_REQKEY_DEFL_SESSION_KEYRING: dest_keyring = key_get(cred->session_keyring); if (dest_keyring) break; - /* fall through */ + fallthrough; case KEY_REQKEY_DEFL_USER_SESSION_KEYRING: ret = look_up_user_keyrings(NULL, &dest_keyring); if (ret < 0) diff --git a/security/loadpin/loadpin.c b/security/loadpin/loadpin.c index 670a1aebb8a1..b12f7d986b1e 100644 --- a/security/loadpin/loadpin.c +++ b/security/loadpin/loadpin.c @@ -11,6 +11,7 @@ #include <linux/module.h> #include <linux/fs.h> +#include <linux/kernel_read_file.h> #include <linux/lsm_hooks.h> #include <linux/mount.h> #include <linux/blkdev.h> @@ -117,11 +118,21 @@ static void loadpin_sb_free_security(struct super_block *mnt_sb) } } -static int loadpin_read_file(struct file *file, enum kernel_read_file_id id) +static int loadpin_read_file(struct file *file, enum kernel_read_file_id id, + bool contents) { struct super_block *load_root; const char *origin = kernel_read_file_id_str(id); + /* + * If we will not know that we'll be seeing the full contents + * then we cannot trust a load will be complete and unchanged + * off disk. Treat all contents=false hooks as if there were + * no associated file struct. + */ + if (!contents) + file = NULL; + /* If the file id is excluded, ignore the pinning. */ if ((unsigned int)id < ARRAY_SIZE(ignore_read_file_id) && ignore_read_file_id[id]) { @@ -176,9 +187,9 @@ static int loadpin_read_file(struct file *file, enum kernel_read_file_id id) return 0; } -static int loadpin_load_data(enum kernel_load_data_id id) +static int loadpin_load_data(enum kernel_load_data_id id, bool contents) { - return loadpin_read_file(NULL, (enum kernel_read_file_id) id); + return loadpin_read_file(NULL, (enum kernel_read_file_id) id, contents); } static struct security_hook_list loadpin_hooks[] __lsm_ro_after_init = { diff --git a/security/safesetid/lsm.c b/security/safesetid/lsm.c index 7760019ad35d..8a176b6adbe5 100644 --- a/security/safesetid/lsm.c +++ b/security/safesetid/lsm.c @@ -24,20 +24,36 @@ /* Flag indicating whether initialization completed */ int safesetid_initialized; -struct setuid_ruleset __rcu *safesetid_setuid_rules; +struct setid_ruleset __rcu *safesetid_setuid_rules; +struct setid_ruleset __rcu *safesetid_setgid_rules; + /* Compute a decision for a transition from @src to @dst under @policy. */ -enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy, - kuid_t src, kuid_t dst) +enum sid_policy_type _setid_policy_lookup(struct setid_ruleset *policy, + kid_t src, kid_t dst) { - struct setuid_rule *rule; + struct setid_rule *rule; enum sid_policy_type result = SIDPOL_DEFAULT; - hash_for_each_possible(policy->rules, rule, next, __kuid_val(src)) { - if (!uid_eq(rule->src_uid, src)) - continue; - if (uid_eq(rule->dst_uid, dst)) - return SIDPOL_ALLOWED; + if (policy->type == UID) { + hash_for_each_possible(policy->rules, rule, next, __kuid_val(src.uid)) { + if (!uid_eq(rule->src_id.uid, src.uid)) + continue; + if (uid_eq(rule->dst_id.uid, dst.uid)) + return SIDPOL_ALLOWED; + result = SIDPOL_CONSTRAINED; + } + } else if (policy->type == GID) { + hash_for_each_possible(policy->rules, rule, next, __kgid_val(src.gid)) { + if (!gid_eq(rule->src_id.gid, src.gid)) + continue; + if (gid_eq(rule->dst_id.gid, dst.gid)){ + return SIDPOL_ALLOWED; + } + result = SIDPOL_CONSTRAINED; + } + } else { + /* Should not reach here, report the ID as contrainsted */ result = SIDPOL_CONSTRAINED; } return result; @@ -47,15 +63,26 @@ enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy, * Compute a decision for a transition from @src to @dst under the active * policy. */ -static enum sid_policy_type setuid_policy_lookup(kuid_t src, kuid_t dst) +static enum sid_policy_type setid_policy_lookup(kid_t src, kid_t dst, enum setid_type new_type) { enum sid_policy_type result = SIDPOL_DEFAULT; - struct setuid_ruleset *pol; + struct setid_ruleset *pol; rcu_read_lock(); - pol = rcu_dereference(safesetid_setuid_rules); - if (pol) - result = _setuid_policy_lookup(pol, src, dst); + if (new_type == UID) + pol = rcu_dereference(safesetid_setuid_rules); + else if (new_type == GID) + pol = rcu_dereference(safesetid_setgid_rules); + else { /* Should not reach here */ + result = SIDPOL_CONSTRAINED; + rcu_read_unlock(); + return result; + } + + if (pol) { + pol->type = new_type; + result = _setid_policy_lookup(pol, src, dst); + } rcu_read_unlock(); return result; } @@ -65,57 +92,101 @@ static int safesetid_security_capable(const struct cred *cred, int cap, unsigned int opts) { - /* We're only interested in CAP_SETUID. */ - if (cap != CAP_SETUID) + /* We're only interested in CAP_SETUID and CAP_SETGID. */ + if (cap != CAP_SETUID && cap != CAP_SETGID) return 0; /* - * If CAP_SETUID is currently used for a set*uid() syscall, we want to + * If CAP_SET{U/G}ID is currently used for a setid() syscall, we want to * let it go through here; the real security check happens later, in the - * task_fix_setuid hook. + * task_fix_set{u/g}id hook. + * + * NOTE: + * Until we add support for restricting setgroups() calls, GID security + * policies offer no meaningful security since we always return 0 here + * when called from within the setgroups() syscall and there is no + * additional hook later on to enforce security policies for setgroups(). */ if ((opts & CAP_OPT_INSETID) != 0) return 0; - /* - * If no policy applies to this task, allow the use of CAP_SETUID for - * other purposes. - */ - if (setuid_policy_lookup(cred->uid, INVALID_UID) == SIDPOL_DEFAULT) + switch (cap) { + case CAP_SETUID: + /* + * If no policy applies to this task, allow the use of CAP_SETUID for + * other purposes. + */ + if (setid_policy_lookup((kid_t){.uid = cred->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT) + return 0; + /* + * Reject use of CAP_SETUID for functionality other than calling + * set*uid() (e.g. setting up userns uid mappings). + */ + pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n", + __kuid_val(cred->uid)); + return -EPERM; + break; + case CAP_SETGID: + /* + * If no policy applies to this task, allow the use of CAP_SETGID for + * other purposes. + */ + if (setid_policy_lookup((kid_t){.gid = cred->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT) + return 0; + /* + * Reject use of CAP_SETUID for functionality other than calling + * set*gid() (e.g. setting up userns gid mappings). + */ + pr_warn("Operation requires CAP_SETGID, which is not available to GID %u for operations besides approved set*gid transitions\n", + __kuid_val(cred->uid)); + return -EPERM; + break; + default: + /* Error, the only capabilities were checking for is CAP_SETUID/GID */ return 0; - - /* - * Reject use of CAP_SETUID for functionality other than calling - * set*uid() (e.g. setting up userns uid mappings). - */ - pr_warn("Operation requires CAP_SETUID, which is not available to UID %u for operations besides approved set*uid transitions\n", - __kuid_val(cred->uid)); - return -EPERM; + break; + } + return 0; } /* * Check whether a caller with old credentials @old is allowed to switch to - * credentials that contain @new_uid. + * credentials that contain @new_id. */ -static bool uid_permitted_for_cred(const struct cred *old, kuid_t new_uid) +static bool id_permitted_for_cred(const struct cred *old, kid_t new_id, enum setid_type new_type) { bool permitted; - /* If our old creds already had this UID in it, it's fine. */ - if (uid_eq(new_uid, old->uid) || uid_eq(new_uid, old->euid) || - uid_eq(new_uid, old->suid)) - return true; + /* If our old creds already had this ID in it, it's fine. */ + if (new_type == UID) { + if (uid_eq(new_id.uid, old->uid) || uid_eq(new_id.uid, old->euid) || + uid_eq(new_id.uid, old->suid)) + return true; + } else if (new_type == GID){ + if (gid_eq(new_id.gid, old->gid) || gid_eq(new_id.gid, old->egid) || + gid_eq(new_id.gid, old->sgid)) + return true; + } else /* Error, new_type is an invalid type */ + return false; /* * Transitions to new UIDs require a check against the policy of the old * RUID. */ permitted = - setuid_policy_lookup(old->uid, new_uid) != SIDPOL_CONSTRAINED; + setid_policy_lookup((kid_t){.uid = old->uid}, new_id, new_type) != SIDPOL_CONSTRAINED; + if (!permitted) { - pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n", - __kuid_val(old->uid), __kuid_val(old->euid), - __kuid_val(old->suid), __kuid_val(new_uid)); + if (new_type == UID) { + pr_warn("UID transition ((%d,%d,%d) -> %d) blocked\n", + __kuid_val(old->uid), __kuid_val(old->euid), + __kuid_val(old->suid), __kuid_val(new_id.uid)); + } else if (new_type == GID) { + pr_warn("GID transition ((%d,%d,%d) -> %d) blocked\n", + __kgid_val(old->gid), __kgid_val(old->egid), + __kgid_val(old->sgid), __kgid_val(new_id.gid)); + } else /* Error, new_type is an invalid type */ + return false; } return permitted; } @@ -131,18 +202,42 @@ static int safesetid_task_fix_setuid(struct cred *new, { /* Do nothing if there are no setuid restrictions for our old RUID. */ - if (setuid_policy_lookup(old->uid, INVALID_UID) == SIDPOL_DEFAULT) + if (setid_policy_lookup((kid_t){.uid = old->uid}, INVALID_ID, UID) == SIDPOL_DEFAULT) + return 0; + + if (id_permitted_for_cred(old, (kid_t){.uid = new->uid}, UID) && + id_permitted_for_cred(old, (kid_t){.uid = new->euid}, UID) && + id_permitted_for_cred(old, (kid_t){.uid = new->suid}, UID) && + id_permitted_for_cred(old, (kid_t){.uid = new->fsuid}, UID)) + return 0; + + /* + * Kill this process to avoid potential security vulnerabilities + * that could arise from a missing allowlist entry preventing a + * privileged process from dropping to a lesser-privileged one. + */ + force_sig(SIGKILL); + return -EACCES; +} + +static int safesetid_task_fix_setgid(struct cred *new, + const struct cred *old, + int flags) +{ + + /* Do nothing if there are no setgid restrictions for our old RGID. */ + if (setid_policy_lookup((kid_t){.gid = old->gid}, INVALID_ID, GID) == SIDPOL_DEFAULT) return 0; - if (uid_permitted_for_cred(old, new->uid) && - uid_permitted_for_cred(old, new->euid) && - uid_permitted_for_cred(old, new->suid) && - uid_permitted_for_cred(old, new->fsuid)) + if (id_permitted_for_cred(old, (kid_t){.gid = new->gid}, GID) && + id_permitted_for_cred(old, (kid_t){.gid = new->egid}, GID) && + id_permitted_for_cred(old, (kid_t){.gid = new->sgid}, GID) && + id_permitted_for_cred(old, (kid_t){.gid = new->fsgid}, GID)) return 0; /* * Kill this process to avoid potential security vulnerabilities - * that could arise from a missing whitelist entry preventing a + * that could arise from a missing allowlist entry preventing a * privileged process from dropping to a lesser-privileged one. */ force_sig(SIGKILL); @@ -151,6 +246,7 @@ static int safesetid_task_fix_setuid(struct cred *new, static struct security_hook_list safesetid_security_hooks[] = { LSM_HOOK_INIT(task_fix_setuid, safesetid_task_fix_setuid), + LSM_HOOK_INIT(task_fix_setgid, safesetid_task_fix_setgid), LSM_HOOK_INIT(capable, safesetid_security_capable) }; diff --git a/security/safesetid/lsm.h b/security/safesetid/lsm.h index db6d16e6bbc3..bde8c43a3767 100644 --- a/security/safesetid/lsm.h +++ b/security/safesetid/lsm.h @@ -27,27 +27,47 @@ enum sid_policy_type { SIDPOL_ALLOWED /* target ID explicitly allowed */ }; +typedef union { + kuid_t uid; + kgid_t gid; +} kid_t; + +enum setid_type { + UID, + GID +}; + /* - * Hash table entry to store safesetid policy signifying that 'src_uid' - * can setuid to 'dst_uid'. + * Hash table entry to store safesetid policy signifying that 'src_id' + * can set*id to 'dst_id'. */ -struct setuid_rule { +struct setid_rule { struct hlist_node next; - kuid_t src_uid; - kuid_t dst_uid; + kid_t src_id; + kid_t dst_id; + + /* Flag to signal if rule is for UID's or GID's */ + enum setid_type type; }; #define SETID_HASH_BITS 8 /* 256 buckets in hash table */ -struct setuid_ruleset { +/* Extension of INVALID_UID/INVALID_GID for kid_t type */ +#define INVALID_ID (kid_t){.uid = INVALID_UID} + +struct setid_ruleset { DECLARE_HASHTABLE(rules, SETID_HASH_BITS); char *policy_str; struct rcu_head rcu; + + //Flag to signal if ruleset is for UID's or GID's + enum setid_type type; }; -enum sid_policy_type _setuid_policy_lookup(struct setuid_ruleset *policy, - kuid_t src, kuid_t dst); +enum sid_policy_type _setid_policy_lookup(struct setid_ruleset *policy, + kid_t src, kid_t dst); -extern struct setuid_ruleset __rcu *safesetid_setuid_rules; +extern struct setid_ruleset __rcu *safesetid_setuid_rules; +extern struct setid_ruleset __rcu *safesetid_setgid_rules; #endif /* _SAFESETID_H */ diff --git a/security/safesetid/securityfs.c b/security/safesetid/securityfs.c index f8bc574cea9c..25310468bcdd 100644 --- a/security/safesetid/securityfs.c +++ b/security/safesetid/securityfs.c @@ -19,22 +19,23 @@ #include "lsm.h" -static DEFINE_MUTEX(policy_update_lock); +static DEFINE_MUTEX(uid_policy_update_lock); +static DEFINE_MUTEX(gid_policy_update_lock); /* - * In the case the input buffer contains one or more invalid UIDs, the kuid_t + * In the case the input buffer contains one or more invalid IDs, the kid_t * variables pointed to by @parent and @child will get updated but this * function will return an error. * Contents of @buf may be modified. */ static int parse_policy_line(struct file *file, char *buf, - struct setuid_rule *rule) + struct setid_rule *rule) { char *child_str; int ret; u32 parsed_parent, parsed_child; - /* Format of |buf| string should be <UID>:<UID>. */ + /* Format of |buf| string should be <UID>:<UID> or <GID>:<GID> */ child_str = strchr(buf, ':'); if (child_str == NULL) return -EINVAL; @@ -49,20 +50,29 @@ static int parse_policy_line(struct file *file, char *buf, if (ret) return ret; - rule->src_uid = make_kuid(file->f_cred->user_ns, parsed_parent); - rule->dst_uid = make_kuid(file->f_cred->user_ns, parsed_child); - if (!uid_valid(rule->src_uid) || !uid_valid(rule->dst_uid)) + if (rule->type == UID){ + rule->src_id.uid = make_kuid(file->f_cred->user_ns, parsed_parent); + rule->dst_id.uid = make_kuid(file->f_cred->user_ns, parsed_child); + if (!uid_valid(rule->src_id.uid) || !uid_valid(rule->dst_id.uid)) + return -EINVAL; + } else if (rule->type == GID){ + rule->src_id.gid = make_kgid(file->f_cred->user_ns, parsed_parent); + rule->dst_id.gid = make_kgid(file->f_cred->user_ns, parsed_child); + if (!gid_valid(rule->src_id.gid) || !gid_valid(rule->dst_id.gid)) + return -EINVAL; + } else { + /* Error, rule->type is an invalid type */ return -EINVAL; - + } return 0; } static void __release_ruleset(struct rcu_head *rcu) { - struct setuid_ruleset *pol = - container_of(rcu, struct setuid_ruleset, rcu); + struct setid_ruleset *pol = + container_of(rcu, struct setid_ruleset, rcu); int bucket; - struct setuid_rule *rule; + struct setid_rule *rule; struct hlist_node *tmp; hash_for_each_safe(pol->rules, bucket, tmp, rule, next) @@ -71,36 +81,55 @@ static void __release_ruleset(struct rcu_head *rcu) kfree(pol); } -static void release_ruleset(struct setuid_ruleset *pol) -{ +static void release_ruleset(struct setid_ruleset *pol){ call_rcu(&pol->rcu, __release_ruleset); } -static void insert_rule(struct setuid_ruleset *pol, struct setuid_rule *rule) +static void insert_rule(struct setid_ruleset *pol, struct setid_rule *rule) { - hash_add(pol->rules, &rule->next, __kuid_val(rule->src_uid)); + if (pol->type == UID) + hash_add(pol->rules, &rule->next, __kuid_val(rule->src_id.uid)); + else if (pol->type == GID) + hash_add(pol->rules, &rule->next, __kgid_val(rule->src_id.gid)); + else /* Error, pol->type is neither UID or GID */ + return; } -static int verify_ruleset(struct setuid_ruleset *pol) +static int verify_ruleset(struct setid_ruleset *pol) { int bucket; - struct setuid_rule *rule, *nrule; + struct setid_rule *rule, *nrule; int res = 0; hash_for_each(pol->rules, bucket, rule, next) { - if (_setuid_policy_lookup(pol, rule->dst_uid, INVALID_UID) == - SIDPOL_DEFAULT) { - pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n", - __kuid_val(rule->src_uid), - __kuid_val(rule->dst_uid)); + if (_setid_policy_lookup(pol, rule->dst_id, INVALID_ID) == SIDPOL_DEFAULT) { + if (pol->type == UID) { + pr_warn("insecure policy detected: uid %d is constrained but transitively unconstrained through uid %d\n", + __kuid_val(rule->src_id.uid), + __kuid_val(rule->dst_id.uid)); + } else if (pol->type == GID) { + pr_warn("insecure policy detected: gid %d is constrained but transitively unconstrained through gid %d\n", + __kgid_val(rule->src_id.gid), + __kgid_val(rule->dst_id.gid)); + } else { /* pol->type is an invalid type */ + res = -EINVAL; + return res; + } res = -EINVAL; /* fix it up */ - nrule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL); + nrule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL); if (!nrule) return -ENOMEM; - nrule->src_uid = rule->dst_uid; - nrule->dst_uid = rule->dst_uid; + if (pol->type == UID){ + nrule->src_id.uid = rule->dst_id.uid; + nrule->dst_id.uid = rule->dst_id.uid; + nrule->type = UID; + } else { /* pol->type must be GID if we've made it to here */ + nrule->src_id.gid = rule->dst_id.gid; + nrule->dst_id.gid = rule->dst_id.gid; + nrule->type = GID; + } insert_rule(pol, nrule); } } @@ -108,16 +137,17 @@ static int verify_ruleset(struct setuid_ruleset *pol) } static ssize_t handle_policy_update(struct file *file, - const char __user *ubuf, size_t len) + const char __user *ubuf, size_t len, enum setid_type policy_type) { - struct setuid_ruleset *pol; + struct setid_ruleset *pol; char *buf, *p, *end; int err; - pol = kmalloc(sizeof(struct setuid_ruleset), GFP_KERNEL); + pol = kmalloc(sizeof(struct setid_ruleset), GFP_KERNEL); if (!pol) return -ENOMEM; pol->policy_str = NULL; + pol->type = policy_type; hash_init(pol->rules); p = buf = memdup_user_nul(ubuf, len); @@ -133,7 +163,7 @@ static ssize_t handle_policy_update(struct file *file, /* policy lines, including the last one, end with \n */ while (*p != '\0') { - struct setuid_rule *rule; + struct setid_rule *rule; end = strchr(p, '\n'); if (end == NULL) { @@ -142,18 +172,18 @@ static ssize_t handle_policy_update(struct file *file, } *end = '\0'; - rule = kmalloc(sizeof(struct setuid_rule), GFP_KERNEL); + rule = kmalloc(sizeof(struct setid_rule), GFP_KERNEL); if (!rule) { err = -ENOMEM; goto out_free_buf; } + rule->type = policy_type; err = parse_policy_line(file, p, rule); if (err) goto out_free_rule; - if (_setuid_policy_lookup(pol, rule->src_uid, rule->dst_uid) == - SIDPOL_ALLOWED) { + if (_setid_policy_lookup(pol, rule->src_id, rule->dst_id) == SIDPOL_ALLOWED) { pr_warn("bad policy: duplicate entry\n"); err = -EEXIST; goto out_free_rule; @@ -178,21 +208,31 @@ out_free_rule: * What we really want here is an xchg() wrapper for RCU, but since that * doesn't currently exist, just use a spinlock for now. */ - mutex_lock(&policy_update_lock); - pol = rcu_replace_pointer(safesetid_setuid_rules, pol, - lockdep_is_held(&policy_update_lock)); - mutex_unlock(&policy_update_lock); + if (policy_type == UID) { + mutex_lock(&uid_policy_update_lock); + pol = rcu_replace_pointer(safesetid_setuid_rules, pol, + lockdep_is_held(&uid_policy_update_lock)); + mutex_unlock(&uid_policy_update_lock); + } else if (policy_type == GID) { + mutex_lock(&gid_policy_update_lock); + pol = rcu_replace_pointer(safesetid_setgid_rules, pol, + lockdep_is_held(&gid_policy_update_lock)); + mutex_unlock(&gid_policy_update_lock); + } else { + /* Error, policy type is neither UID or GID */ + pr_warn("error: bad policy type"); + } err = len; out_free_buf: kfree(buf); out_free_pol: if (pol) - release_ruleset(pol); + release_ruleset(pol); return err; } -static ssize_t safesetid_file_write(struct file *file, +static ssize_t safesetid_uid_file_write(struct file *file, const char __user *buf, size_t len, loff_t *ppos) @@ -203,38 +243,74 @@ static ssize_t safesetid_file_write(struct file *file, if (*ppos != 0) return -EINVAL; - return handle_policy_update(file, buf, len); + return handle_policy_update(file, buf, len, UID); +} + +static ssize_t safesetid_gid_file_write(struct file *file, + const char __user *buf, + size_t len, + loff_t *ppos) +{ + if (!file_ns_capable(file, &init_user_ns, CAP_MAC_ADMIN)) + return -EPERM; + + if (*ppos != 0) + return -EINVAL; + + return handle_policy_update(file, buf, len, GID); } static ssize_t safesetid_file_read(struct file *file, char __user *buf, - size_t len, loff_t *ppos) + size_t len, loff_t *ppos, struct mutex *policy_update_lock, struct __rcu setid_ruleset* ruleset) { ssize_t res = 0; - struct setuid_ruleset *pol; + struct setid_ruleset *pol; const char *kbuf; - mutex_lock(&policy_update_lock); - pol = rcu_dereference_protected(safesetid_setuid_rules, - lockdep_is_held(&policy_update_lock)); + mutex_lock(policy_update_lock); + pol = rcu_dereference_protected(ruleset, lockdep_is_held(policy_update_lock)); if (pol) { kbuf = pol->policy_str; res = simple_read_from_buffer(buf, len, ppos, kbuf, strlen(kbuf)); } - mutex_unlock(&policy_update_lock); + mutex_unlock(policy_update_lock); + return res; } -static const struct file_operations safesetid_file_fops = { - .read = safesetid_file_read, - .write = safesetid_file_write, +static ssize_t safesetid_uid_file_read(struct file *file, char __user *buf, + size_t len, loff_t *ppos) +{ + return safesetid_file_read(file, buf, len, ppos, + &uid_policy_update_lock, safesetid_setuid_rules); +} + +static ssize_t safesetid_gid_file_read(struct file *file, char __user *buf, + size_t len, loff_t *ppos) +{ + return safesetid_file_read(file, buf, len, ppos, + &gid_policy_update_lock, safesetid_setgid_rules); +} + + + +static const struct file_operations safesetid_uid_file_fops = { + .read = safesetid_uid_file_read, + .write = safesetid_uid_file_write, +}; + +static const struct file_operations safesetid_gid_file_fops = { + .read = safesetid_gid_file_read, + .write = safesetid_gid_file_write, }; static int __init safesetid_init_securityfs(void) { int ret; struct dentry *policy_dir; - struct dentry *policy_file; + struct dentry *uid_policy_file; + struct dentry *gid_policy_file; if (!safesetid_initialized) return 0; @@ -245,13 +321,21 @@ static int __init safesetid_init_securityfs(void) goto error; } - policy_file = securityfs_create_file("whitelist_policy", 0600, - policy_dir, NULL, &safesetid_file_fops); - if (IS_ERR(policy_file)) { - ret = PTR_ERR(policy_file); + uid_policy_file = securityfs_create_file("uid_allowlist_policy", 0600, + policy_dir, NULL, &safesetid_uid_file_fops); + if (IS_ERR(uid_policy_file)) { + ret = PTR_ERR(uid_policy_file); goto error; } + gid_policy_file = securityfs_create_file("gid_allowlist_policy", 0600, + policy_dir, NULL, &safesetid_gid_file_fops); + if (IS_ERR(gid_policy_file)) { + ret = PTR_ERR(gid_policy_file); + goto error; + } + + return 0; error: diff --git a/security/security.c b/security/security.c index 70a7ad357bc6..a28045dc9e7f 100644 --- a/security/security.c +++ b/security/security.c @@ -16,6 +16,7 @@ #include <linux/export.h> #include <linux/init.h> #include <linux/kernel.h> +#include <linux/kernel_read_file.h> #include <linux/lsm_hooks.h> #include <linux/integrity.h> #include <linux/ima.h> @@ -1671,14 +1672,15 @@ int security_kernel_module_request(char *kmod_name) return integrity_kernel_module_request(kmod_name); } -int security_kernel_read_file(struct file *file, enum kernel_read_file_id id) +int security_kernel_read_file(struct file *file, enum kernel_read_file_id id, + bool contents) { int ret; - ret = call_int_hook(kernel_read_file, 0, file, id); + ret = call_int_hook(kernel_read_file, 0, file, id, contents); if (ret) return ret; - return ima_read_file(file, id); + return ima_read_file(file, id, contents); } EXPORT_SYMBOL_GPL(security_kernel_read_file); @@ -1694,17 +1696,31 @@ int security_kernel_post_read_file(struct file *file, char *buf, loff_t size, } EXPORT_SYMBOL_GPL(security_kernel_post_read_file); -int security_kernel_load_data(enum kernel_load_data_id id) +int security_kernel_load_data(enum kernel_load_data_id id, bool contents) { int ret; - ret = call_int_hook(kernel_load_data, 0, id); + ret = call_int_hook(kernel_load_data, 0, id, contents); if (ret) return ret; - return ima_load_data(id); + return ima_load_data(id, contents); } EXPORT_SYMBOL_GPL(security_kernel_load_data); +int security_kernel_post_load_data(char *buf, loff_t size, + enum kernel_load_data_id id, + char *description) +{ + int ret; + + ret = call_int_hook(kernel_post_load_data, 0, buf, size, id, + description); + if (ret) + return ret; + return ima_post_load_data(buf, size, id, description); +} +EXPORT_SYMBOL_GPL(security_kernel_post_load_data); + int security_task_fix_setuid(struct cred *new, const struct cred *old, int flags) { diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index d6b182c11700..6b1826fc3658 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -24,6 +24,7 @@ #include <linux/init.h> #include <linux/kd.h> #include <linux/kernel.h> +#include <linux/kernel_read_file.h> #include <linux/tracehook.h> #include <linux/errno.h> #include <linux/sched/signal.h> @@ -3609,26 +3610,20 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, switch (cmd) { case FIONREAD: - /* fall through */ case FIBMAP: - /* fall through */ case FIGETBSZ: - /* fall through */ case FS_IOC_GETFLAGS: - /* fall through */ case FS_IOC_GETVERSION: error = file_has_perm(cred, file, FILE__GETATTR); break; case FS_IOC_SETFLAGS: - /* fall through */ case FS_IOC_SETVERSION: error = file_has_perm(cred, file, FILE__SETATTR); break; /* sys_ioctl() checks */ case FIONBIO: - /* fall through */ case FIOASYNC: error = file_has_perm(cred, file, 0); break; @@ -3786,7 +3781,7 @@ static int selinux_file_fcntl(struct file *file, unsigned int cmd, err = file_has_perm(cred, file, FILE__WRITE); break; } - /* fall through */ + fallthrough; case F_SETOWN: case F_SETSIG: case F_GETFL: @@ -4011,13 +4006,14 @@ static int selinux_kernel_module_from_file(struct file *file) } static int selinux_kernel_read_file(struct file *file, - enum kernel_read_file_id id) + enum kernel_read_file_id id, + bool contents) { int rc = 0; switch (id) { case READING_MODULE: - rc = selinux_kernel_module_from_file(file); + rc = selinux_kernel_module_from_file(contents ? file : NULL); break; default: break; @@ -4026,7 +4022,7 @@ static int selinux_kernel_read_file(struct file *file, return rc; } -static int selinux_kernel_load_data(enum kernel_load_data_id id) +static int selinux_kernel_load_data(enum kernel_load_data_id id, bool contents) { int rc = 0; diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index 408d306895f8..d338962fb0c4 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -535,7 +535,7 @@ int mls_compute_sid(struct policydb *p, scontext, tcontext); } - /* Fallthrough */ + fallthrough; case AVTAB_CHANGE: if ((tclass == p->process_class) || sock) /* Use the process MLS attributes. */ @@ -546,8 +546,6 @@ int mls_compute_sid(struct policydb *p, case AVTAB_MEMBER: /* Use the process effective MLS attributes. */ return mls_context_cpy_low(newcontext, scontext); - - /* fall through */ } return -EINVAL; } diff --git a/security/smack/smack.h b/security/smack/smack.h index e9e817d09785..a9768b12716b 100644 --- a/security/smack/smack.h +++ b/security/smack/smack.h @@ -100,7 +100,12 @@ struct socket_smack { struct smack_known *smk_out; /* outbound label */ struct smack_known *smk_in; /* inbound label */ struct smack_known *smk_packet; /* TCP peer label */ + int smk_state; /* netlabel socket states */ }; +#define SMK_NETLBL_UNSET 0 +#define SMK_NETLBL_UNLABELED 1 +#define SMK_NETLBL_LABELED 2 +#define SMK_NETLBL_REQSKB 3 /* * Inode smack data @@ -197,19 +202,6 @@ enum { #define SMACK_CIPSO_OPTION "-CIPSO" /* - * How communications on this socket are treated. - * Usually it's determined by the underlying netlabel code - * but there are certain cases, including single label hosts - * and potentially single label interfaces for which the - * treatment can not be known in advance. - * - * The possibility of additional labeling schemes being - * introduced in the future exists as well. - */ -#define SMACK_UNLABELED_SOCKET 0 -#define SMACK_CIPSO_SOCKET 1 - -/* * CIPSO defaults. */ #define SMACK_CIPSO_DOI_DEFAULT 3 /* Historical */ @@ -305,6 +297,7 @@ struct smack_known *smk_find_entry(const char *); bool smack_privileged(int cap); bool smack_privileged_cred(int cap, const struct cred *cred); void smk_destroy_label_list(struct list_head *list); +int smack_populate_secattr(struct smack_known *skp); /* * Shared data. diff --git a/security/smack/smack_access.c b/security/smack/smack_access.c index 38ac3da4e791..efe2406a3960 100644 --- a/security/smack/smack_access.c +++ b/security/smack/smack_access.c @@ -511,6 +511,42 @@ int smk_netlbl_mls(int level, char *catset, struct netlbl_lsm_secattr *sap, } /** + * smack_populate_secattr - fill in the smack_known netlabel information + * @skp: pointer to the structure to fill + * + * Populate the netlabel secattr structure for a Smack label. + * + * Returns 0 unless creating the category mapping fails + */ +int smack_populate_secattr(struct smack_known *skp) +{ + int slen; + + skp->smk_netlabel.attr.secid = skp->smk_secid; + skp->smk_netlabel.domain = skp->smk_known; + skp->smk_netlabel.cache = netlbl_secattr_cache_alloc(GFP_ATOMIC); + if (skp->smk_netlabel.cache != NULL) { + skp->smk_netlabel.flags |= NETLBL_SECATTR_CACHE; + skp->smk_netlabel.cache->free = NULL; + skp->smk_netlabel.cache->data = skp; + } + skp->smk_netlabel.flags |= NETLBL_SECATTR_SECID | + NETLBL_SECATTR_MLS_LVL | + NETLBL_SECATTR_DOMAIN; + /* + * If direct labeling works use it. + * Otherwise use mapped labeling. + */ + slen = strlen(skp->smk_known); + if (slen < SMK_CIPSOLEN) + return smk_netlbl_mls(smack_cipso_direct, skp->smk_known, + &skp->smk_netlabel, slen); + + return smk_netlbl_mls(smack_cipso_mapped, (char *)&skp->smk_secid, + &skp->smk_netlabel, sizeof(skp->smk_secid)); +} + +/** * smk_import_entry - import a label, return the list entry * @string: a text string that might be a Smack label * @len: the maximum size, or zero if it is NULL terminated. @@ -523,7 +559,6 @@ struct smack_known *smk_import_entry(const char *string, int len) { struct smack_known *skp; char *smack; - int slen; int rc; smack = smk_parse_smack(string, len); @@ -544,21 +579,8 @@ struct smack_known *smk_import_entry(const char *string, int len) skp->smk_known = smack; skp->smk_secid = smack_next_secid++; - skp->smk_netlabel.domain = skp->smk_known; - skp->smk_netlabel.flags = - NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL; - /* - * If direct labeling works use it. - * Otherwise use mapped labeling. - */ - slen = strlen(smack); - if (slen < SMK_CIPSOLEN) - rc = smk_netlbl_mls(smack_cipso_direct, skp->smk_known, - &skp->smk_netlabel, slen); - else - rc = smk_netlbl_mls(smack_cipso_mapped, (char *)&skp->smk_secid, - &skp->smk_netlabel, sizeof(skp->smk_secid)); + rc = smack_populate_secattr(skp); if (rc >= 0) { INIT_LIST_HEAD(&skp->smk_rules); mutex_init(&skp->smk_rules_lock); @@ -569,9 +591,6 @@ struct smack_known *smk_import_entry(const char *string, int len) smk_insert_entry(skp); goto unlockout; } - /* - * smk_netlbl_mls failed. - */ kfree(skp); skp = ERR_PTR(rc); freeout: diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 8ffbf951b7ed..5c90b9fa4d40 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -2383,38 +2383,31 @@ static struct smack_known *smack_ipv6host_label(struct sockaddr_in6 *sip) } /** - * smack_netlabel - Set the secattr on a socket + * smack_netlbl_add - Set the secattr on a socket * @sk: the socket - * @labeled: socket label scheme * - * Convert the outbound smack value (smk_out) to a - * secattr and attach it to the socket. + * Attach the outbound smack value (smk_out) to the socket. * * Returns 0 on success or an error code */ -static int smack_netlabel(struct sock *sk, int labeled) +static int smack_netlbl_add(struct sock *sk) { - struct smack_known *skp; struct socket_smack *ssp = sk->sk_security; - int rc = 0; + struct smack_known *skp = ssp->smk_out; + int rc; - /* - * Usually the netlabel code will handle changing the - * packet labeling based on the label. - * The case of a single label host is different, because - * a single label host should never get a labeled packet - * even though the label is usually associated with a packet - * label. - */ local_bh_disable(); bh_lock_sock_nested(sk); - if (ssp->smk_out == smack_net_ambient || - labeled == SMACK_UNLABELED_SOCKET) - netlbl_sock_delattr(sk); - else { - skp = ssp->smk_out; - rc = netlbl_sock_setattr(sk, sk->sk_family, &skp->smk_netlabel); + rc = netlbl_sock_setattr(sk, sk->sk_family, &skp->smk_netlabel); + switch (rc) { + case 0: + ssp->smk_state = SMK_NETLBL_LABELED; + break; + case -EDESTADDRREQ: + ssp->smk_state = SMK_NETLBL_REQSKB; + rc = 0; + break; } bh_unlock_sock(sk); @@ -2424,7 +2417,31 @@ static int smack_netlabel(struct sock *sk, int labeled) } /** - * smack_netlbel_send - Set the secattr on a socket and perform access checks + * smack_netlbl_delete - Remove the secattr from a socket + * @sk: the socket + * + * Remove the outbound smack value from a socket + */ +static void smack_netlbl_delete(struct sock *sk) +{ + struct socket_smack *ssp = sk->sk_security; + + /* + * Take the label off the socket if one is set. + */ + if (ssp->smk_state != SMK_NETLBL_LABELED) + return; + + local_bh_disable(); + bh_lock_sock_nested(sk); + netlbl_sock_delattr(sk); + bh_unlock_sock(sk); + local_bh_enable(); + ssp->smk_state = SMK_NETLBL_UNLABELED; +} + +/** + * smk_ipv4_check - Perform IPv4 host access checks * @sk: the socket * @sap: the destination address * @@ -2434,11 +2451,10 @@ static int smack_netlabel(struct sock *sk, int labeled) * Returns 0 on success or an error code. * */ -static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) +static int smk_ipv4_check(struct sock *sk, struct sockaddr_in *sap) { struct smack_known *skp; - int rc; - int sk_lbl; + int rc = 0; struct smack_known *hkp; struct socket_smack *ssp = sk->sk_security; struct smk_audit_info ad; @@ -2454,19 +2470,18 @@ static int smack_netlabel_send(struct sock *sk, struct sockaddr_in *sap) ad.a.u.net->dport = sap->sin_port; ad.a.u.net->v4info.daddr = sap->sin_addr.s_addr; #endif - sk_lbl = SMACK_UNLABELED_SOCKET; skp = ssp->smk_out; rc = smk_access(skp, hkp, MAY_WRITE, &ad); rc = smk_bu_note("IPv4 host check", skp, hkp, MAY_WRITE, rc); - } else { - sk_lbl = SMACK_CIPSO_SOCKET; - rc = 0; + /* + * Clear the socket netlabel if it's set. + */ + if (!rc) + smack_netlbl_delete(sk); } rcu_read_unlock(); - if (rc != 0) - return rc; - return smack_netlabel(sk, sk_lbl); + return rc; } /** @@ -2703,7 +2718,7 @@ static int smack_inode_setsecurity(struct inode *inode, const char *name, else if (strcmp(name, XATTR_SMACK_IPOUT) == 0) { ssp->smk_out = skp; if (sock->sk->sk_family == PF_INET) { - rc = smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); + rc = smack_netlbl_add(sock->sk); if (rc != 0) printk(KERN_WARNING "Smack: \"%s\" netlbl error %d.\n", @@ -2754,7 +2769,7 @@ static int smack_socket_post_create(struct socket *sock, int family, /* * Set the outbound netlbl. */ - return smack_netlabel(sock->sk, SMACK_CIPSO_SOCKET); + return smack_netlbl_add(sock->sk); } /** @@ -2845,7 +2860,7 @@ static int smack_socket_connect(struct socket *sock, struct sockaddr *sap, } if (sap->sa_family != AF_INET || addrlen < sizeof(struct sockaddr_in)) return 0; - rc = smack_netlabel_send(sock->sk, (struct sockaddr_in *)sap); + rc = smk_ipv4_check(sock->sk, (struct sockaddr_in *)sap); return rc; } @@ -3365,7 +3380,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) * to set mount options simulate setting the * superblock default. */ - /* Fall through */ + fallthrough; default: /* * This isn't an understood special case. @@ -3663,7 +3678,7 @@ static int smack_socket_sendmsg(struct socket *sock, struct msghdr *msg, if (msg->msg_namelen < sizeof(struct sockaddr_in) || sip->sin_family != AF_INET) return -EINVAL; - rc = smack_netlabel_send(sock->sk, sip); + rc = smk_ipv4_check(sock->sk, sip); break; #if IS_ENABLED(CONFIG_IPV6) case AF_INET6: @@ -3700,6 +3715,18 @@ static struct smack_known *smack_from_secattr(struct netlbl_lsm_secattr *sap, int acat; int kcat; + /* + * Netlabel found it in the cache. + */ + if ((sap->flags & NETLBL_SECATTR_CACHE) != 0) + return (struct smack_known *)sap->cache->data; + + if ((sap->flags & NETLBL_SECATTR_SECID) != 0) + /* + * Looks like a fallback, which gives us a secid. + */ + return smack_from_secid(sap->attr.secid); + if ((sap->flags & NETLBL_SECATTR_MLS_LVL) != 0) { /* * Looks like a CIPSO packet. @@ -3747,11 +3774,6 @@ static struct smack_known *smack_from_secattr(struct netlbl_lsm_secattr *sap, return &smack_known_web; return &smack_known_star; } - if ((sap->flags & NETLBL_SECATTR_SECID) != 0) - /* - * Looks like a fallback, which gives us a secid. - */ - return smack_from_secid(sap->attr.secid); /* * Without guidance regarding the smack value * for the packet fall back on the network @@ -3811,6 +3833,62 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr_in6 *sip) #endif /* CONFIG_IPV6 */ /** + * smack_from_skb - Smack data from the secmark in an skb + * @skb: packet + * + * Returns smack_known of the secmark or NULL if that won't work. + */ +#ifdef CONFIG_NETWORK_SECMARK +static struct smack_known *smack_from_skb(struct sk_buff *skb) +{ + if (skb == NULL || skb->secmark == 0) + return NULL; + + return smack_from_secid(skb->secmark); +} +#else +static inline struct smack_known *smack_from_skb(struct sk_buff *skb) +{ + return NULL; +} +#endif + +/** + * smack_from_netlbl - Smack data from the IP options in an skb + * @sk: socket data came in on + * @family: address family + * @skb: packet + * + * Find the Smack label in the IP options. If it hasn't been + * added to the netlabel cache, add it here. + * + * Returns smack_known of the IP options or NULL if that won't work. + */ +static struct smack_known *smack_from_netlbl(struct sock *sk, u16 family, + struct sk_buff *skb) +{ + struct netlbl_lsm_secattr secattr; + struct socket_smack *ssp = NULL; + struct smack_known *skp = NULL; + int rc; + + netlbl_secattr_init(&secattr); + + if (sk) + ssp = sk->sk_security; + + if (netlbl_skbuff_getattr(skb, family, &secattr) == 0) { + skp = smack_from_secattr(&secattr, ssp); + if (secattr.flags & NETLBL_SECATTR_CACHEABLE) + rc = netlbl_cache_add(skb, family, &skp->smk_netlabel); + } + + netlbl_secattr_destroy(&secattr); + + return skp; +} + +/** * smack_socket_sock_rcv_skb - Smack packet delivery access check * @sk: socket * @skb: packet @@ -3819,7 +3897,6 @@ static int smk_skb_to_addr_ipv6(struct sk_buff *skb, struct sockaddr_in6 *sip) */ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) { - struct netlbl_lsm_secattr secattr; struct socket_smack *ssp = sk->sk_security; struct smack_known *skp = NULL; int rc = 0; @@ -3838,33 +3915,18 @@ static int smack_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) switch (family) { case PF_INET: -#ifdef CONFIG_SECURITY_SMACK_NETFILTER /* * If there is a secmark use it rather than the CIPSO label. * If there is no secmark fall back to CIPSO. * The secmark is assumed to reflect policy better. */ - if (skb && skb->secmark != 0) { - skp = smack_from_secid(skb->secmark); - goto access_check; + skp = smack_from_skb(skb); + if (skp == NULL) { + skp = smack_from_netlbl(sk, family, skb); + if (skp == NULL) + skp = smack_net_ambient; } -#endif /* CONFIG_SECURITY_SMACK_NETFILTER */ - /* - * Translate what netlabel gave us. - */ - netlbl_secattr_init(&secattr); - rc = netlbl_skbuff_getattr(skb, family, &secattr); - if (rc == 0) - skp = smack_from_secattr(&secattr, ssp); - else - skp = smack_net_ambient; - - netlbl_secattr_destroy(&secattr); - -#ifdef CONFIG_SECURITY_SMACK_NETFILTER -access_check: -#endif #ifdef CONFIG_AUDIT smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); ad.a.u.net->family = family; @@ -3890,16 +3952,14 @@ access_check: proto != IPPROTO_TCP && proto != IPPROTO_DCCP) break; #ifdef SMACK_IPV6_SECMARK_LABELING - if (skb && skb->secmark != 0) - skp = smack_from_secid(skb->secmark); - else if (smk_ipv6_localhost(&sadd)) - break; - else + skp = smack_from_skb(skb); + if (skp == NULL) { + if (smk_ipv6_localhost(&sadd)) + break; skp = smack_ipv6host_label(&sadd); - if (skp == NULL) - skp = smack_net_ambient; - if (skb == NULL) - break; + if (skp == NULL) + skp = smack_net_ambient; + } #ifdef CONFIG_AUDIT smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); ad.a.u.net->family = family; @@ -3971,12 +4031,11 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid) { - struct netlbl_lsm_secattr secattr; struct socket_smack *ssp = NULL; struct smack_known *skp; + struct sock *sk = NULL; int family = PF_UNSPEC; u32 s = 0; /* 0 is the invalid secid */ - int rc; if (skb != NULL) { if (skb->protocol == htons(ETH_P_IP)) @@ -3995,27 +4054,25 @@ static int smack_socket_getpeersec_dgram(struct socket *sock, s = ssp->smk_out->smk_secid; break; case PF_INET: -#ifdef CONFIG_SECURITY_SMACK_NETFILTER - s = skb->secmark; - if (s != 0) + skp = smack_from_skb(skb); + if (skp) { + s = skp->smk_secid; break; -#endif + } /* * Translate what netlabel gave us. */ - if (sock != NULL && sock->sk != NULL) - ssp = sock->sk->sk_security; - netlbl_secattr_init(&secattr); - rc = netlbl_skbuff_getattr(skb, family, &secattr); - if (rc == 0) { - skp = smack_from_secattr(&secattr, ssp); + if (sock != NULL) + sk = sock->sk; + skp = smack_from_netlbl(sk, family, skb); + if (skp != NULL) s = skp->smk_secid; - } - netlbl_secattr_destroy(&secattr); break; case PF_INET6: #ifdef SMACK_IPV6_SECMARK_LABELING - s = skb->secmark; + skp = smack_from_skb(skb); + if (skp) + s = skp->smk_secid; #endif break; } @@ -4063,7 +4120,6 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, u16 family = sk->sk_family; struct smack_known *skp; struct socket_smack *ssp = sk->sk_security; - struct netlbl_lsm_secattr secattr; struct sockaddr_in addr; struct iphdr *hdr; struct smack_known *hskp; @@ -4087,29 +4143,17 @@ static int smack_inet_conn_request(struct sock *sk, struct sk_buff *skb, } #endif /* CONFIG_IPV6 */ -#ifdef CONFIG_SECURITY_SMACK_NETFILTER /* * If there is a secmark use it rather than the CIPSO label. * If there is no secmark fall back to CIPSO. * The secmark is assumed to reflect policy better. */ - if (skb && skb->secmark != 0) { - skp = smack_from_secid(skb->secmark); - goto access_check; + skp = smack_from_skb(skb); + if (skp == NULL) { + skp = smack_from_netlbl(sk, family, skb); + if (skp == NULL) + skp = &smack_known_huh; } -#endif /* CONFIG_SECURITY_SMACK_NETFILTER */ - - netlbl_secattr_init(&secattr); - rc = netlbl_skbuff_getattr(skb, family, &secattr); - if (rc == 0) - skp = smack_from_secattr(&secattr, ssp); - else - skp = &smack_known_huh; - netlbl_secattr_destroy(&secattr); - -#ifdef CONFIG_SECURITY_SMACK_NETFILTER -access_check: -#endif #ifdef CONFIG_AUDIT smk_ad_init_net(&ad, __func__, LSM_AUDIT_DATA_NET, &net); diff --git a/security/smack/smackfs.c b/security/smack/smackfs.c index 9c4308077574..e567b4baf3a0 100644 --- a/security/smack/smackfs.c +++ b/security/smack/smackfs.c @@ -922,6 +922,10 @@ static ssize_t smk_set_cipso(struct file *file, const char __user *buf, skp->smk_netlabel.attr.mls.cat = ncats.attr.mls.cat; skp->smk_netlabel.attr.mls.lvl = ncats.attr.mls.lvl; rc = count; + /* + * This mapping may have been cached, so clear the cache. + */ + netlbl_cache_invalidate(); } out: @@ -2950,15 +2954,6 @@ static struct file_system_type smk_fs_type = { static struct vfsmount *smackfs_mount; -static int __init smk_preset_netlabel(struct smack_known *skp) -{ - skp->smk_netlabel.domain = skp->smk_known; - skp->smk_netlabel.flags = - NETLBL_SECATTR_DOMAIN | NETLBL_SECATTR_MLS_LVL; - return smk_netlbl_mls(smack_cipso_direct, skp->smk_known, - &skp->smk_netlabel, strlen(skp->smk_known)); -} - /** * init_smk_fs - get the smackfs superblock * @@ -2997,19 +2992,19 @@ static int __init init_smk_fs(void) smk_cipso_doi(); smk_unlbl_ambient(NULL); - rc = smk_preset_netlabel(&smack_known_floor); + rc = smack_populate_secattr(&smack_known_floor); if (err == 0 && rc < 0) err = rc; - rc = smk_preset_netlabel(&smack_known_hat); + rc = smack_populate_secattr(&smack_known_hat); if (err == 0 && rc < 0) err = rc; - rc = smk_preset_netlabel(&smack_known_huh); + rc = smack_populate_secattr(&smack_known_huh); if (err == 0 && rc < 0) err = rc; - rc = smk_preset_netlabel(&smack_known_star); + rc = smack_populate_secattr(&smack_known_star); if (err == 0 && rc < 0) err = rc; - rc = smk_preset_netlabel(&smack_known_web); + rc = smack_populate_secattr(&smack_known_web); if (err == 0 && rc < 0) err = rc; diff --git a/security/tomoyo/common.c b/security/tomoyo/common.c index c16b8c1b03e7..4bee32bfe16d 100644 --- a/security/tomoyo/common.c +++ b/security/tomoyo/common.c @@ -1240,7 +1240,7 @@ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head, tomoyo_set_space(head); tomoyo_set_string(head, cond->transit->name); } - /* fall through */ + fallthrough; case 1: { const u16 condc = cond->condc; @@ -1345,12 +1345,12 @@ static bool tomoyo_print_condition(struct tomoyo_io_buffer *head, } } head->r.cond_step++; - /* fall through */ + fallthrough; case 2: if (!tomoyo_flush(head)) break; head->r.cond_step++; - /* fall through */ + fallthrough; case 3: if (cond->grant_log != TOMOYO_GRANTLOG_AUTO) tomoyo_io_printf(head, " grant_log=%s", @@ -1639,7 +1639,7 @@ static void tomoyo_read_domain(struct tomoyo_io_buffer *head) tomoyo_set_string(head, tomoyo_dif[i]); head->r.index = 0; head->r.step++; - /* fall through */ + fallthrough; case 1: while (head->r.index < TOMOYO_MAX_ACL_GROUPS) { i = head->r.index++; @@ -1652,14 +1652,14 @@ static void tomoyo_read_domain(struct tomoyo_io_buffer *head) head->r.index = 0; head->r.step++; tomoyo_set_lf(head); - /* fall through */ + fallthrough; case 2: if (!tomoyo_read_domain2(head, &domain->acl_info_list)) return; head->r.step++; if (!tomoyo_set_lf(head)) return; - /* fall through */ + fallthrough; case 3: head->r.step = 0; if (head->r.print_this_domain_only) @@ -2088,7 +2088,7 @@ int tomoyo_supervisor(struct tomoyo_request_info *r, const char *fmt, ...) /* Check max_learning_entry parameter. */ if (tomoyo_domain_quota_is_ok(r)) break; - /* fall through */ + fallthrough; default: return 0; } @@ -2710,13 +2710,13 @@ ssize_t tomoyo_write_control(struct tomoyo_io_buffer *head, case TOMOYO_DOMAINPOLICY: if (tomoyo_select_domain(head, cp0)) continue; - /* fall through */ + fallthrough; case TOMOYO_EXCEPTIONPOLICY: if (!strcmp(cp0, "select transition_only")) { head->r.print_transition_related_only = true; continue; } - /* fall through */ + fallthrough; default: if (!tomoyo_manager()) { error = -EPERM; diff --git a/security/tomoyo/file.c b/security/tomoyo/file.c index 86f7d1b90212..051f7297877c 100644 --- a/security/tomoyo/file.c +++ b/security/tomoyo/file.c @@ -927,7 +927,7 @@ int tomoyo_path2_perm(const u8 operation, const struct path *path1, case TOMOYO_TYPE_LINK: if (!d_is_dir(path1->dentry)) break; - /* fall through */ + fallthrough; case TOMOYO_TYPE_PIVOT_ROOT: tomoyo_add_slash(&buf1); tomoyo_add_slash(&buf2); diff --git a/security/tomoyo/util.c b/security/tomoyo/util.c index eba0b3395851..a40abb0b91ee 100644 --- a/security/tomoyo/util.c +++ b/security/tomoyo/util.c @@ -143,6 +143,8 @@ char *tomoyo_read_token(struct tomoyo_acl_param *param) return pos; } +static bool tomoyo_correct_path2(const char *filename, const size_t len); + /** * tomoyo_get_domainname - Read a domainname from a line. * @@ -157,10 +159,10 @@ const struct tomoyo_path_info *tomoyo_get_domainname char *pos = start; while (*pos) { - if (*pos++ != ' ' || *pos++ == '/') + if (*pos++ != ' ' || + tomoyo_correct_path2(pos, strchrnul(pos, ' ') - pos)) continue; - pos -= 2; - *pos++ = '\0'; + *(pos - 1) = '\0'; break; } param->data = pos; @@ -514,6 +516,22 @@ bool tomoyo_correct_word(const char *string) } /** + * tomoyo_correct_path2 - Check whether the given pathname follows the naming rules. + * + * @filename: The pathname to check. + * @len: Length of @filename. + * + * Returns true if @filename follows the naming rules, false otherwise. + */ +static bool tomoyo_correct_path2(const char *filename, const size_t len) +{ + const char *cp1 = memchr(filename, '/', len); + const char *cp2 = memchr(filename, '.', len); + + return cp1 && (!cp2 || (cp1 < cp2)) && tomoyo_correct_word2(filename, len); +} + +/** * tomoyo_correct_path - Validate a pathname. * * @filename: The pathname to check. @@ -523,7 +541,7 @@ bool tomoyo_correct_word(const char *string) */ bool tomoyo_correct_path(const char *filename) { - return *filename == '/' && tomoyo_correct_word(filename); + return tomoyo_correct_path2(filename, strlen(filename)); } /** @@ -545,8 +563,7 @@ bool tomoyo_correct_domain(const unsigned char *domainname) if (!cp) break; - if (*domainname != '/' || - !tomoyo_correct_word2(domainname, cp - domainname)) + if (!tomoyo_correct_path2(domainname, cp - domainname)) return false; domainname = cp + 1; } diff --git a/security/yama/yama_lsm.c b/security/yama/yama_lsm.c index 536c99646f6a..06e226166aab 100644 --- a/security/yama/yama_lsm.c +++ b/security/yama/yama_lsm.c @@ -99,7 +99,7 @@ static void report_access(const char *access, struct task_struct *target, info->access = access; info->target = target; info->agent = agent; - if (task_work_add(current, &info->work, true) == 0) + if (task_work_add(current, &info->work, TWA_RESUME) == 0) return; /* success */ WARN(1, "report_access called from exiting task"); |