diff options
Diffstat (limited to 'security')
75 files changed, 2375 insertions, 770 deletions
diff --git a/security/Kconfig.hardening b/security/Kconfig.hardening index 6923036e1a2f..81c81ad983ad 100644 --- a/security/Kconfig.hardening +++ b/security/Kconfig.hardening @@ -278,7 +278,7 @@ config CC_HAS_RANDSTRUCT choice prompt "Randomize layout of sensitive kernel structures" - default RANDSTRUCT_FULL if COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) + default RANDSTRUCT_FULL if !(RUST_IS_AVAILABLE && HAVE_RUST) && COMPILE_TEST && (GCC_PLUGINS || CC_HAS_RANDSTRUCT) default RANDSTRUCT_NONE help If you enable this, the layouts of structures that are entirely diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 5b42140e12e8..d1386722537d 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2073,7 +2073,7 @@ fail2: return error; } -static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, +static struct dentry *ns_mkdir_op(const struct mnt_idmap *idmap, struct inode *dir, struct dentry *dentry, umode_t mode) { struct aa_ns *ns, *parent; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index d502ad0ac26f..c73681d820a0 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -397,7 +397,7 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d label = begin_current_label_crit_section(&needput); if (!unconfined(label)) { - struct mnt_idmap *idmap = mnt_idmap(old_dir->mnt); + const struct mnt_idmap *idmap = mnt_idmap(old_dir->mnt); vfsuid_t vfsuid; struct path old_path = { .mnt = old_dir->mnt, .dentry = old_dentry }; @@ -485,7 +485,7 @@ static int apparmor_file_open(struct file *file) label = aa_get_newest_cred_label_condref(file->f_cred, &needput); if (!unconfined(label)) { - struct mnt_idmap *idmap = file_mnt_idmap(file); + const struct mnt_idmap *idmap = file_mnt_idmap(file); struct inode *inode = file_inode(file); vfsuid_t vfsuid; struct path_cond cond = { diff --git a/security/commoncap.c b/security/commoncap.c index 3399535808fe..d47ab3022343 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -348,7 +348,7 @@ int cap_inode_need_killpriv(struct dentry *dentry) * * Return: 0 if successful, -ve on error. */ -int cap_inode_killpriv(struct mnt_idmap *idmap, struct dentry *dentry) +int cap_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry) { int error; @@ -417,7 +417,7 @@ static bool is_v3header(int size, const struct vfs_cap_data *cap) * by the integrity subsystem, which really wants the unconverted values - * so that's good. */ -int cap_inode_getsecurity(struct mnt_idmap *idmap, +int cap_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) { @@ -566,7 +566,7 @@ static bool validheader(size_t size, const struct vfs_cap_data *cap) * * Return: On success, return the new size; on error, return < 0. */ -int cap_convert_nscap(struct mnt_idmap *idmap, struct dentry *dentry, +int cap_convert_nscap(const struct mnt_idmap *idmap, struct dentry *dentry, const void **ivalue, size_t size) { struct vfs_ns_cap_data *nscap; @@ -672,7 +672,7 @@ static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps, * permissions. On non-idmapped mounts or if permission checking is to be * performed on the raw inode simply pass @nop_mnt_idmap. */ -int get_vfs_caps_from_disk(struct mnt_idmap *idmap, +int get_vfs_caps_from_disk(const struct mnt_idmap *idmap, const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps) { @@ -1063,7 +1063,7 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name, * This is used to make sure security xattrs don't get removed by those who * aren't privileged to remove them. */ -int cap_inode_removexattr(struct mnt_idmap *idmap, +int cap_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { struct user_namespace *user_ns = dentry->d_sb->s_user_ns; diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index b59e3f121b8a..ac8328812e6e 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -481,7 +481,7 @@ static enum integrity_status evm_verify_current_integrity(struct dentry *dentry) * * Returns 1 if passed xattr value differs from current value, 0 otherwise. */ -static int evm_xattr_change(struct mnt_idmap *idmap, +static int evm_xattr_change(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { @@ -517,7 +517,7 @@ out: * For posix xattr acls only, permit security.evm, even if it currently * doesn't exist, to be updated unless the EVM signature is immutable. */ -static int evm_protect_xattr(struct mnt_idmap *idmap, +static int evm_protect_xattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len) { @@ -563,7 +563,7 @@ static int evm_protect_xattr(struct mnt_idmap *idmap, dentry->d_inode, dentry->d_name.name, "update_metadata", integrity_status_msg[evm_status], - -EPERM, 0); + -EPERM, 0, -EPERM); } out: /* Exception if the HMAC is not going to be calculated. */ @@ -588,7 +588,7 @@ out: integrity_audit_msg(AUDIT_INTEGRITY_METADATA, d_backing_inode(dentry), dentry->d_name.name, "appraise_metadata", integrity_status_msg[evm_status], - -EPERM, 0); + -EPERM, 0, -EPERM); return evm_status == INTEGRITY_PASS ? 0 : -EPERM; } @@ -607,7 +607,7 @@ out: * userspace from writing HMAC value. Writing 'security.evm' requires * requires CAP_SYS_ADMIN privileges. */ -static int evm_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len, int flags) { @@ -639,7 +639,7 @@ static int evm_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, * Removing 'security.evm' requires CAP_SYS_ADMIN privileges and that * the current value is valid. */ -static int evm_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name) { /* Policy permits modification of the protected xattrs even though @@ -652,7 +652,7 @@ static int evm_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, } #ifdef CONFIG_FS_POSIX_ACL -static int evm_inode_set_acl_change(struct mnt_idmap *idmap, +static int evm_inode_set_acl_change(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, struct posix_acl *kacl) { @@ -671,7 +671,7 @@ static int evm_inode_set_acl_change(struct mnt_idmap *idmap, return 0; } #else -static inline int evm_inode_set_acl_change(struct mnt_idmap *idmap, +static inline int evm_inode_set_acl_change(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, struct posix_acl *kacl) @@ -693,7 +693,7 @@ static inline int evm_inode_set_acl_change(struct mnt_idmap *idmap, * * Return: zero on success, -EPERM on failure. */ -static int evm_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { enum integrity_status evm_status; @@ -729,7 +729,7 @@ static int evm_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, integrity_audit_msg(AUDIT_INTEGRITY_METADATA, d_backing_inode(dentry), dentry->d_name.name, "appraise_metadata", integrity_status_msg[evm_status], - -EPERM, 0); + -EPERM, 0, -EPERM); return -EPERM; } @@ -745,7 +745,7 @@ static int evm_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, * * Return: zero on success, -EPERM on failure. */ -static int evm_inode_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return evm_inode_set_acl(idmap, dentry, acl_name, NULL); @@ -926,14 +926,14 @@ static void evm_inode_post_removexattr(struct dentry *dentry, * Update the 'security.evm' xattr with the EVM HMAC re-calculated after * removing posix acls. */ -static inline void evm_inode_post_remove_acl(struct mnt_idmap *idmap, +static inline void evm_inode_post_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { evm_inode_post_removexattr(dentry, acl_name); } -static int evm_attr_change(struct mnt_idmap *idmap, +static int evm_attr_change(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { struct inode *inode = d_backing_inode(dentry); @@ -956,7 +956,7 @@ static int evm_attr_change(struct mnt_idmap *idmap, * Permit update of file attributes when files have a valid EVM signature, * except in the case of them having an immutable portable signature. */ -static int evm_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int evm_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { unsigned int ia_valid = attr->ia_valid; @@ -992,7 +992,8 @@ static int evm_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, integrity_audit_msg(AUDIT_INTEGRITY_METADATA, d_backing_inode(dentry), dentry->d_name.name, "appraise_metadata", - integrity_status_msg[evm_status], -EPERM, 0); + integrity_status_msg[evm_status], -EPERM, 0, + -EPERM); return -EPERM; } @@ -1008,7 +1009,7 @@ static int evm_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, * This function is called from notify_change(), which expects the caller * to lock the inode's i_mutex. */ -static void evm_inode_post_setattr(struct mnt_idmap *idmap, +static void evm_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) { if (!evm_revalidate_status(NULL)) @@ -1140,7 +1141,7 @@ static void evm_file_release(struct file *file) iint->flags &= ~EVM_NEW_FILE; } -static void evm_post_path_mknod(struct mnt_idmap *idmap, struct dentry *dentry) +static void evm_post_path_mknod(const struct mnt_idmap *idmap, struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); struct evm_iint_cache *iint = evm_iint_inode(inode); diff --git a/security/integrity/ima/Kconfig b/security/integrity/ima/Kconfig index b3a9f86809b0..72654cf797cd 100644 --- a/security/integrity/ima/Kconfig +++ b/security/integrity/ima/Kconfig @@ -46,12 +46,16 @@ config IMA_KEXEC config IMA_MEASURE_PCR_IDX int + prompt "PCR Index for Aggregate" if EXPERT 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. + measurement list. Most attestation tooling expects PCR 10. + + The default is almost always what you want. Only change this + if you know what you are doing. config IMA_LSM_RULES bool diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 10214f73ca1e..b502854f28ee 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -423,7 +423,7 @@ static inline void ima_process_queued_keys(void) {} #endif /* CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS */ /* LIM API function definitions */ -int ima_get_action(struct mnt_idmap *idmap, struct inode *inode, +int ima_get_action(const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, int mask, enum ima_hooks func, int *pcr, struct ima_template_desc **template_desc, @@ -437,7 +437,7 @@ void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, struct ima_template_desc *template_desc); -int process_buffer_measurement(struct mnt_idmap *idmap, +int process_buffer_measurement(const struct mnt_idmap *idmap, struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, int pcr, const char *func_data, @@ -454,7 +454,7 @@ void ima_free_template_entry(struct ima_template_entry *entry); const char *ima_d_path(const struct path *path, char **pathbuf, char *filename); /* IMA policy related functions */ -int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode, +int ima_match_policy(const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, enum ima_hooks func, int mask, int flags, int *pcr, struct ima_template_desc **template_desc, @@ -489,7 +489,7 @@ int ima_appraise_measurement(enum ima_hooks func, struct ima_iint_cache *iint, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, bool bprm_is_check); -int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode, +int ima_must_appraise(const struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func); void ima_update_xattr(struct ima_iint_cache *iint, struct file *file); enum integrity_status ima_get_cache_status(struct ima_iint_cache *iint, @@ -519,7 +519,7 @@ static inline int ima_appraise_measurement(enum ima_hooks func, return INTEGRITY_UNKNOWN; } -static inline int ima_must_appraise(struct mnt_idmap *idmap, +static inline int ima_must_appraise(const struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func) { diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 122d127e108d..45701f04ba62 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -116,7 +116,7 @@ int ima_store_template(struct ima_template_entry *entry, if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, template_name, op, - audit_cause, result, 0); + audit_cause, result, 0, result); return result; } } @@ -159,7 +159,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename, ima_free_template_entry(entry); err_out: integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, - op, cause, result, 0); + op, cause, result, 0, result); } /** @@ -188,7 +188,7 @@ err_out: * Returns IMA_MEASURE, IMA_APPRAISE mask. * */ -int ima_get_action(struct mnt_idmap *idmap, struct inode *inode, +int ima_get_action(const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, int mask, enum ima_hooks func, int *pcr, struct ima_template_desc **template_desc, @@ -330,7 +330,7 @@ out: integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, filename.name.name, "collect_data", - audit_cause, result, 0); + audit_cause, result, 0, result); release_dentry_name_snapshot(&filename); } @@ -383,7 +383,7 @@ void ima_store_measurement(struct ima_iint_cache *iint, struct file *file, result = ima_alloc_init_template(&event_data, &entry, template_desc); if (result < 0) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, - op, audit_cause, result, 0); + op, audit_cause, result, 0, result); return; } diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index ced2e131b061..134174d2e3f0 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -71,7 +71,7 @@ bool is_ima_appraise_enabled(void) * * Return 1 to appraise or hash */ -int ima_must_appraise(struct mnt_idmap *idmap, struct inode *inode, +int ima_must_appraise(const struct mnt_idmap *idmap, struct inode *inode, int mask, enum ima_hooks func) { struct lsm_prop prop; @@ -564,7 +564,7 @@ out: status = INTEGRITY_FAIL; cause = "unverifiable-signature"; integrity_audit_msg(audit_msgno, inode, filename, - op, cause, rc, 0); + op, cause, rc, 0, rc < 0 ? rc : 0); } else if (status != INTEGRITY_PASS) { /* Fix mode, but don't replace file signatures. */ if ((ima_appraise & IMA_APPRAISE_FIX) && !try_modsig && @@ -589,7 +589,7 @@ out: } integrity_audit_msg(audit_msgno, inode, filename, - op, cause, rc, 0); + op, cause, rc, 0, rc < 0 ? rc : 0); } else { ima_cache_flags(iint, func); } @@ -634,7 +634,7 @@ void ima_update_xattr(struct ima_iint_cache *iint, struct file *file) * This function is called from notify_change(), which expects the caller * to lock the inode's i_mutex. */ -static void ima_inode_post_setattr(struct mnt_idmap *idmap, +static void ima_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) { struct inode *inode = d_backing_inode(dentry); @@ -748,16 +748,18 @@ static int validate_hash_algo(struct dentry *dentry, return -EACCES; path = dentry_path(dentry, pathbuf, PATH_MAX); + if (IS_ERR(path)) + path = NULL; integrity_audit_msg(AUDIT_INTEGRITY_DATA, d_inode(dentry), path, - "set_data", errmsg, -EACCES, 0); + "set_data", errmsg, -EACCES, 0, -EACCES); kfree(pathbuf); return -EACCES; } -static int ima_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int ima_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len, int flags) { @@ -790,7 +792,7 @@ static int ima_inode_setxattr(struct mnt_idmap *idmap, struct dentry *dentry, return result; } -static int ima_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, +static int ima_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { if (evm_revalidate_status(acl_name)) @@ -799,7 +801,7 @@ static int ima_inode_set_acl(struct mnt_idmap *idmap, struct dentry *dentry, return 0; } -static int ima_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int ima_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *xattr_name) { int result, digsig = -1; @@ -815,7 +817,7 @@ static int ima_inode_removexattr(struct mnt_idmap *idmap, struct dentry *dentry, return result; } -static int ima_inode_remove_acl(struct mnt_idmap *idmap, struct dentry *dentry, +static int ima_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return ima_inode_set_acl(idmap, dentry, acl_name, NULL); diff --git a/security/integrity/ima/ima_fs.c b/security/integrity/ima/ima_fs.c index 2a0bca554316..3b8174a91a34 100644 --- a/security/integrity/ima/ima_fs.c +++ b/security/integrity/ima/ima_fs.c @@ -592,11 +592,11 @@ static ssize_t ima_write_policy(struct file *file, const char __user *buf, if (data[0] == '/') { result = ima_read_policy(data); } else if (ima_appraise & IMA_APPRAISE_POLICY) { + result = -EACCES; pr_err("signed policy file (specified as an absolute pathname) required\n"); integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, "policy_update", "signed policy required", - 1, 0); - result = -EACCES; + result, 0, result); } else { ima_measure_raw_policy(data, datalen); result = ima_parse_add_rule(data); @@ -745,7 +745,8 @@ static int ima_release_policy(struct inode *inode, struct file *file) pr_info("policy update %s\n", cause); integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, - "policy_update", cause, !valid_policy, 0); + "policy_update", cause, !valid_policy, 0, + !valid_policy ? -EINVAL : 0); if (!valid_policy) { ima_delete_rules(); diff --git a/security/integrity/ima/ima_init.c b/security/integrity/ima/ima_init.c index d53f4d89a53e..7a76e2686267 100644 --- a/security/integrity/ima/ima_init.c +++ b/security/integrity/ima/ima_init.c @@ -103,7 +103,7 @@ static int __init ima_add_boot_aggregate(void) return 0; err_out: integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, filename, op, - audit_cause, result, 0); + audit_cause, result, 0, result); return result; } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index ab1e53b3210d..fed80da3ee09 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -119,7 +119,7 @@ static int mmap_violation_check(enum ima_hooks func, struct file *file, *pathname = ima_d_path(&file->f_path, pathbuf, filename); integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, *pathname, - "mmap_file", "mmapped_writers", rc, 0); + "mmap_file", "mmapped_writers", rc, 0, rc); } return rc; } @@ -466,7 +466,7 @@ static int process_measurement(struct file *file, const struct cred *cred, integrity_audit_msg(AUDIT_INTEGRITY_DATA, file_inode(file), pathname, "collect_data", - "denied-hash-algorithm", rc, 0); + "denied-hash-algorithm", rc, 0, rc); } out_locked: if ((mask & MAY_WRITE) && test_bit(IMA_DIGSIG, &iint->atomic_flags) && @@ -579,7 +579,8 @@ static int ima_file_mprotect(struct vm_area_struct *vma, unsigned long reqprot, file = vma->vm_file; pathname = ima_d_path(&file->f_path, &pathbuf, filename); integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, pathname, - "collect_data", "failed-mprotect", result, 0); + "collect_data", "failed-mprotect", result, 0, + result); if (pathbuf) __putname(pathbuf); @@ -846,7 +847,7 @@ EXPORT_SYMBOL_GPL(ima_inode_hash); * Skip calling process_measurement(), but indicate which newly, created * tmpfiles are in policy. */ -static void ima_post_create_tmpfile(struct mnt_idmap *idmap, +static void ima_post_create_tmpfile(const struct mnt_idmap *idmap, struct inode *inode) { @@ -879,7 +880,7 @@ static void ima_post_create_tmpfile(struct mnt_idmap *idmap, * Mark files created via the mknodat syscall as new, so that the * file data can be written later. */ -static void ima_post_path_mknod(struct mnt_idmap *idmap, struct dentry *dentry) +static void ima_post_path_mknod(const struct mnt_idmap *idmap, struct dentry *dentry) { struct ima_iint_cache *iint; struct inode *inode = dentry->d_inode; @@ -1095,7 +1096,7 @@ static int ima_post_load_data(char *buf, loff_t size, * has been written to the passed location but not added to a measurement entry, * a negative value otherwise. */ -int process_buffer_measurement(struct mnt_idmap *idmap, +int process_buffer_measurement(const struct mnt_idmap *idmap, struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, int pcr, const char *func_data, @@ -1195,9 +1196,9 @@ int process_buffer_measurement(struct mnt_idmap *idmap, out: if (ret < 0) - integrity_audit_message(AUDIT_INTEGRITY_PCR, NULL, eventname, - func_measure_str(func), - audit_cause, ret, 0, ret); + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, eventname, + func_measure_str(func), + audit_cause, ret, 0, ret); return ret; } diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index f79d07bb63c6..2dc1d93aa768 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -165,7 +165,10 @@ static struct ima_rule_entry dont_measure_rules[] __ro_after_init = { {.action = DONT_MEASURE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_MEASURE, .fsmagic = NSFS_MAGIC, .flags = IMA_FSMAGIC}, - {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC} + {.action = DONT_MEASURE, .fsmagic = EFIVARFS_MAGIC, + .flags = IMA_FSMAGIC}, + {.action = DONT_MEASURE, .fsmagic = CONFIGFS_MAGIC, + .flags = IMA_FSMAGIC} }; static struct ima_rule_entry original_measurement_rules[] __ro_after_init = { @@ -211,6 +214,8 @@ static struct ima_rule_entry default_appraise_rules[] __ro_after_init = { {.action = DONT_APPRAISE, .fsmagic = EFIVARFS_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP_SUPER_MAGIC, .flags = IMA_FSMAGIC}, {.action = DONT_APPRAISE, .fsmagic = CGROUP2_SUPER_MAGIC, .flags = IMA_FSMAGIC}, + {.action = DONT_APPRAISE, .fsmagic = CONFIGFS_MAGIC, + .flags = IMA_FSMAGIC}, #ifdef CONFIG_IMA_WRITE_POLICY {.action = APPRAISE, .func = POLICY_CHECK, .flags = IMA_FUNC | IMA_DIGSIG_REQUIRED}, @@ -575,7 +580,7 @@ static bool ima_match_rule_data(struct ima_rule_entry *rule, * Returns true on rule match, false on failure. */ static bool ima_match_rules(struct ima_rule_entry *rule, - struct mnt_idmap *idmap, + const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, enum ima_hooks func, int mask, const char *func_data) @@ -757,7 +762,7 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func) * list when walking it. Reads are many orders of magnitude more numerous * than writes so ima_match_policy() is classical RCU candidate. */ -int ima_match_policy(struct mnt_idmap *idmap, struct inode *inode, +int ima_match_policy(const struct mnt_idmap *idmap, struct inode *inode, const struct cred *cred, struct lsm_prop *prop, enum ima_hooks func, int mask, int flags, int *pcr, struct ima_template_desc **template_desc, @@ -1987,7 +1992,8 @@ ssize_t ima_parse_add_rule(char *rule) entry = kzalloc_obj(*entry); if (!entry) { integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, - NULL, op, "-ENOMEM", -ENOMEM, audit_info); + NULL, op, "-ENOMEM", -ENOMEM, audit_info, + -ENOMEM); return -ENOMEM; } @@ -1998,7 +2004,7 @@ ssize_t ima_parse_add_rule(char *rule) ima_free_rule(entry); integrity_audit_msg(AUDIT_INTEGRITY_STATUS, NULL, NULL, op, "invalid-policy", result, - audit_info); + audit_info, result); return result; } @@ -2416,7 +2422,7 @@ void ima_measure_loaded_policy(void) rule = kmalloc(rule_len, GFP_KERNEL); if (!rule) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, event_name, - op, "ENOMEM", result, 0); + op, "ENOMEM", result, 0, result); return; } @@ -2435,7 +2441,7 @@ void ima_measure_loaded_policy(void) result = -E2BIG; integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, event_name, op, "rule_length", - result, 0); + result, 0, result); rcu_read_unlock(); goto free_rule; } @@ -2449,7 +2455,7 @@ void ima_measure_loaded_policy(void) file.buf = kmalloc(file_len, GFP_KERNEL); if (!file.buf) { integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, event_name, - op, "ENOMEM", result, 0); + op, "ENOMEM", result, 0, result); goto free_rule; } diff --git a/security/integrity/ima/ima_queue.c b/security/integrity/ima/ima_queue.c index f89f0ca3d4ed..67782c004cdf 100644 --- a/security/integrity/ima/ima_queue.c +++ b/security/integrity/ima/ima_queue.c @@ -75,8 +75,7 @@ static struct hlist_head *ima_alloc_replace_htable(void) struct hlist_head *old_htable, *new_htable; /* Initializing to zeros is equivalent to call HLIST_HEAD_INIT. */ - new_htable = kcalloc(IMA_MEASURE_HTABLE_SIZE, sizeof(struct hlist_head), - GFP_KERNEL); + new_htable = kzalloc_objs(struct hlist_head, IMA_MEASURE_HTABLE_SIZE); if (!new_htable) return ERR_PTR(-ENOMEM); @@ -292,7 +291,7 @@ int ima_add_template_entry(struct ima_template_entry *entry, int violation, out: mutex_unlock(&ima_extend_list_mutex); integrity_audit_msg(AUDIT_INTEGRITY_PCR, inode, filename, - op, audit_cause, result, audit_info); + op, audit_cause, result, audit_info, result); return result; } diff --git a/security/integrity/ima/ima_queue_keys.c b/security/integrity/ima/ima_queue_keys.c index b5ed33cbb272..8adc8863ad5a 100644 --- a/security/integrity/ima/ima_queue_keys.c +++ b/security/integrity/ima/ima_queue_keys.c @@ -90,10 +90,10 @@ static struct ima_key_entry *ima_alloc_key_entry(struct key *keyring, out: if (rc) { - integrity_audit_message(AUDIT_INTEGRITY_PCR, NULL, - keyring->description, - func_measure_str(KEY_CHECK), - audit_cause, rc, 0, rc); + integrity_audit_msg(AUDIT_INTEGRITY_PCR, NULL, + keyring->description, + func_measure_str(KEY_CHECK), + audit_cause, rc, 0, rc); ima_free_key_entry(entry); entry = NULL; } diff --git a/security/integrity/ima/ima_template.c b/security/integrity/ima/ima_template.c index 7034573fb41e..16154078fd70 100644 --- a/security/integrity/ima/ima_template.c +++ b/security/integrity/ima/ima_template.c @@ -450,6 +450,11 @@ int ima_restore_measurement_list(loff_t size, void *buf) return -EINVAL; } + if (khdr->buffer_size > size) { + pr_err("attempting to restore a corrupted measurement list"); + return -EINVAL; + } + bitmap_zero(hdr_mask, HDR__LAST); bitmap_set(hdr_mask, HDR_PCR, 1); bitmap_set(hdr_mask, HDR_DIGEST, 1); @@ -524,6 +529,13 @@ int ima_restore_measurement_list(loff_t size, void *buf) ret = -EINVAL; break; } + } else { + /* + * If the template hash found in the kexec buffer is all + * zeros, record it as a violation. Note that this can + * be a false positive. + */ + atomic_long_inc(&ima_num_violations); } entry->pcr = !ima_canonical_fmt ? *(u32 *)(hdr[HDR_PCR].data) : diff --git a/security/integrity/ima/ima_template_lib.c b/security/integrity/ima/ima_template_lib.c index 8a89236f926c..9bd80ca1159a 100644 --- a/security/integrity/ima/ima_template_lib.c +++ b/security/integrity/ima/ima_template_lib.c @@ -392,7 +392,7 @@ int ima_eventdigest_init(struct ima_event_data *event_data, if (result) { integrity_audit_msg(AUDIT_INTEGRITY_DATA, inode, event_data->filename, "collect_data", - "failed", result, 0); + "failed", result, 0, result); return result; } cur_digest = hash_hdr->digest; diff --git a/security/integrity/integrity.h b/security/integrity/integrity.h index 0c581c03c5da..52c5ec881cca 100644 --- a/security/integrity/integrity.h +++ b/security/integrity/integrity.h @@ -218,12 +218,8 @@ static inline void evm_load_x509(void) /* 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); - -void integrity_audit_message(int audit_msgno, struct inode *inode, - const unsigned char *fname, const char *op, - const char *cause, int result, int info, - int errno); + const char *cause, int result, int info, + int errno); static inline struct audit_buffer * integrity_audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type) @@ -235,15 +231,7 @@ integrity_audit_log_start(struct audit_context *ctx, gfp_t gfp_mask, int type) static inline void integrity_audit_msg(int audit_msgno, struct inode *inode, const unsigned char *fname, const char *op, const char *cause, - int result, int info) -{ -} - -static inline void integrity_audit_message(int audit_msgno, - struct inode *inode, - const unsigned char *fname, - const char *op, const char *cause, - int result, int info, int errno) + int result, int info, int errno) { } diff --git a/security/integrity/integrity_audit.c b/security/integrity/integrity_audit.c index d8d9e5ff1cd2..22106aaeb0df 100644 --- a/security/integrity/integrity_audit.c +++ b/security/integrity/integrity_audit.c @@ -27,16 +27,8 @@ __setup("integrity_audit=", integrity_audit_setup); 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) -{ - integrity_audit_message(audit_msgno, inode, fname, op, cause, - result, audit_info, 0); -} - -void integrity_audit_message(int audit_msgno, struct inode *inode, - const unsigned char *fname, const char *op, - const char *cause, int result, int audit_info, - int errno) + const char *cause, int result, int audit_info, + int errno) { struct audit_buffer *ab; char name[TASK_COMM_LEN]; diff --git a/security/ipe/audit.c b/security/ipe/audit.c index 93fb59fbddd6..b6f8fb54085a 100644 --- a/security/ipe/audit.c +++ b/security/ipe/audit.c @@ -34,34 +34,34 @@ "new_policy_digest=" IPE_AUDIT_HASH_ALG ":" static const char *const audit_op_names[__IPE_OP_MAX + 1] = { - "EXECUTE", - "FIRMWARE", - "KMODULE", - "KEXEC_IMAGE", - "KEXEC_INITRAMFS", - "POLICY", - "X509_CERT", - "UNKNOWN", + [IPE_OP_EXEC] = "EXECUTE", + [IPE_OP_FIRMWARE] = "FIRMWARE", + [IPE_OP_KERNEL_MODULE] = "KMODULE", + [IPE_OP_KEXEC_IMAGE] = "KEXEC_IMAGE", + [IPE_OP_KEXEC_INITRAMFS] = "KEXEC_INITRAMFS", + [IPE_OP_POLICY] = "POLICY", + [IPE_OP_X509] = "X509_CERT", + [IPE_OP_INVALID] = "UNKNOWN", }; static const char *const audit_hook_names[__IPE_HOOK_MAX] = { - "BPRM_CHECK", - "BPRM_CREDS_FOR_EXEC", - "MMAP", - "MPROTECT", - "KERNEL_READ", - "KERNEL_LOAD", + [IPE_HOOK_BPRM_CHECK] = "BPRM_CHECK", + [IPE_HOOK_BPRM_CREDS_FOR_EXEC] = "BPRM_CREDS_FOR_EXEC", + [IPE_HOOK_MMAP] = "MMAP", + [IPE_HOOK_MPROTECT] = "MPROTECT", + [IPE_HOOK_KERNEL_READ] = "KERNEL_READ", + [IPE_HOOK_KERNEL_LOAD] = "KERNEL_LOAD", }; static const char *const audit_prop_names[__IPE_PROP_MAX] = { - "boot_verified=FALSE", - "boot_verified=TRUE", - "dmverity_roothash=", - "dmverity_signature=FALSE", - "dmverity_signature=TRUE", - "fsverity_digest=", - "fsverity_signature=FALSE", - "fsverity_signature=TRUE", + [IPE_PROP_BOOT_VERIFIED_FALSE] = "boot_verified=FALSE", + [IPE_PROP_BOOT_VERIFIED_TRUE] = "boot_verified=TRUE", + [IPE_PROP_DMV_ROOTHASH] = "dmverity_roothash=", + [IPE_PROP_DMV_SIG_FALSE] = "dmverity_signature=FALSE", + [IPE_PROP_DMV_SIG_TRUE] = "dmverity_signature=TRUE", + [IPE_PROP_FSV_DIGEST] = "fsverity_digest=", + [IPE_PROP_FSV_SIG_FALSE] = "fsverity_signature=FALSE", + [IPE_PROP_FSV_SIG_TRUE] = "fsverity_signature=TRUE", }; /** @@ -262,18 +262,10 @@ void ipe_audit_policy_load(const struct ipe_policy *const p) */ void ipe_audit_enforce(bool new_enforce, bool old_enforce) { - struct audit_buffer *ab; - - ab = audit_log_start(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS); - if (!ab) - return; - audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS, "enforcing=%d old_enforcing=%d auid=%u ses=%u" " enabled=1 old-enabled=1 lsm=ipe res=1", new_enforce, old_enforce, from_kuid(&init_user_ns, audit_get_loginuid(current)), audit_get_sessionid(current)); - - audit_log_end(ab); } diff --git a/security/ipe/eval.c b/security/ipe/eval.c index 21439c5be336..4a7be96c84f0 100644 --- a/security/ipe/eval.c +++ b/security/ipe/eval.c @@ -134,10 +134,14 @@ static bool evaluate_boot_verified(const struct ipe_eval_ctx *const ctx) static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, struct ipe_prop *p) { - return !!ctx->ipe_bdev && - !!ctx->ipe_bdev->root_hash && - ipe_digest_eval(p->value, - ctx->ipe_bdev->root_hash); + const struct digest_info *root_hash; + + if (!ctx->ipe_bdev) + return false; + + root_hash = rcu_dereference(ctx->ipe_bdev->root_hash); + + return root_hash && ipe_digest_eval(p->value, root_hash); } #else static bool evaluate_dmv_roothash(const struct ipe_eval_ctx *const ctx, diff --git a/security/ipe/eval.h b/security/ipe/eval.h index fef65a36468c..b26d1147d070 100644 --- a/security/ipe/eval.h +++ b/security/ipe/eval.h @@ -27,7 +27,7 @@ struct ipe_bdev { #ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE bool dm_verity_signed; #endif /* CONFIG_IPE_PROP_DM_VERITY_SIGNATURE */ - struct digest_info *root_hash; + struct digest_info __rcu *root_hash; }; #endif /* CONFIG_IPE_PROP_DM_VERITY */ diff --git a/security/ipe/fs.c b/security/ipe/fs.c index 076c111c85c8..847a76afb93d 100644 --- a/security/ipe/fs.c +++ b/security/ipe/fs.c @@ -159,18 +159,16 @@ static ssize_t new_policy(struct file *f, const char __user *data, } rc = ipe_new_policyfs_node(p); - if (rc) - goto out; out: kfree(copy); if (rc < 0) { ipe_free_policy(p); ipe_audit_policy_load(ERR_PTR(rc)); - } else { - ipe_audit_policy_load(p); + return rc; } - return (rc < 0) ? rc : len; + + return len; } static const struct file_operations np_fops = { diff --git a/security/ipe/hooks.c b/security/ipe/hooks.c index 0ae54a880405..d878b52ceec8 100644 --- a/security/ipe/hooks.c +++ b/security/ipe/hooks.c @@ -9,6 +9,7 @@ #include <linux/binfmts.h> #include <linux/mman.h> #include <linux/blk_types.h> +#include <linux/rcupdate.h> #include "ipe.h" #include "hooks.h" @@ -232,7 +233,20 @@ void ipe_bdev_free_security(struct block_device *bdev) { struct ipe_bdev *blob = ipe_bdev(bdev); - ipe_digest_free(blob->root_hash); + ipe_digest_free(rcu_access_pointer(blob->root_hash)); +} + +static void ipe_set_dmverity_roothash(struct ipe_bdev *blob, + struct digest_info *info) +{ + struct digest_info *old; + + /* Protected by device-mapper's md->suspend_lock */ + old = rcu_replace_pointer(blob->root_hash, info, true); + if (old) { + synchronize_rcu(); + ipe_digest_free(old); + } } #ifdef CONFIG_IPE_PROP_DM_VERITY_SIGNATURE @@ -280,8 +294,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ return -EINVAL; if (!value) { - ipe_digest_free(blob->root_hash); - blob->root_hash = NULL; + ipe_set_dmverity_roothash(blob, NULL); return 0; } @@ -301,8 +314,7 @@ int ipe_bdev_setintegrity(struct block_device *bdev, enum lsm_integrity_type typ info->digest_len = digest->digest_len; - ipe_digest_free(blob->root_hash); - blob->root_hash = info; + ipe_set_dmverity_roothash(blob, info); return 0; err: diff --git a/security/ipe/policy.c b/security/ipe/policy.c index 827867be4fac..e542329726bc 100644 --- a/security/ipe/policy.c +++ b/security/ipe/policy.c @@ -52,7 +52,7 @@ void ipe_free_policy(struct ipe_policy *p) ipe_del_policyfs_node(p); ipe_free_parsed_policy(p->parsed); /* - * p->text is allocated only when p->pkcs7 is not NULL + * p->text is allocated only when p->pkcs7 is NULL * otherwise it points to the plaintext data inside the pkcs7 */ if (!p->pkcs7) diff --git a/security/ipe/policy_fs.c b/security/ipe/policy_fs.c index 9d92d8a14b13..a7aeb57483c6 100644 --- a/security/ipe/policy_fs.c +++ b/security/ipe/policy_fs.c @@ -481,6 +481,9 @@ int ipe_new_policyfs_node(struct ipe_policy *p) inode_lock(root); p->policyfs = policyfs; root->i_private = p; + /* Only audit signed policies from userspace */ + if (p->pkcs7) + ipe_audit_policy_load(p); inode_unlock(root); return 0; diff --git a/security/ipe/policy_parser.c b/security/ipe/policy_parser.c index 6fa5bebf8471..309a61594e1d 100644 --- a/security/ipe/policy_parser.c +++ b/security/ipe/policy_parser.c @@ -128,6 +128,8 @@ static const match_table_t header_tokens = { {__IPE_HEADER_MAX, NULL} }; +static_assert(ARRAY_SIZE(header_tokens) == __IPE_HEADER_MAX + 1); + /** * parse_header() - Parse policy header information. * @line: Supplies header line to be parsed. @@ -201,7 +203,7 @@ out: * * %false - The token is not "DEFAULT" * * %true - The token is "DEFAULT" */ -static bool token_default(char *token) +static bool token_default(const char *token) { return !strcmp(token, "DEFAULT"); } @@ -240,6 +242,8 @@ static const match_table_t operation_tokens = { {IPE_OP_INVALID, NULL} }; +static_assert(ARRAY_SIZE(operation_tokens) == __IPE_OP_MAX + 1); + /** * parse_operation() - Parse the operation type given a token string. * @t: Supplies the token string to be parsed. @@ -259,6 +263,8 @@ static const match_table_t action_tokens = { {IPE_ACTION_INVALID, NULL} }; +static_assert(ARRAY_SIZE(action_tokens) == __IPE_ACTION_MAX + 1); + /** * parse_action() - Parse the action type given a token string. * @t: Supplies the token string to be parsed. @@ -284,6 +290,8 @@ static const match_table_t property_tokens = { {IPE_PROP_INVALID, NULL} }; +static_assert(ARRAY_SIZE(property_tokens) == __IPE_PROP_MAX + 1); + /** * parse_property() - Parse a rule property given a token string. * @t: Supplies the token string to be parsed. diff --git a/security/keys/encrypted-keys/encrypted.c b/security/keys/encrypted-keys/encrypted.c index 59cb77b237b3..e07092ea301a 100644 --- a/security/keys/encrypted-keys/encrypted.c +++ b/security/keys/encrypted-keys/encrypted.c @@ -19,6 +19,7 @@ #include <linux/parser.h> #include <linux/string.h> #include <linux/err.h> +#include <linux/overflow.h> #include <keys/user-type.h> #include <keys/trusted-type.h> #include <keys/encrypted-type.h> @@ -579,6 +580,7 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, { struct encrypted_key_payload *epayload = NULL; unsigned short datablob_len; + unsigned short payload_totallen; unsigned short decrypted_datalen; unsigned short payload_datalen; unsigned int encrypted_datalen; @@ -632,16 +634,22 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key, encrypted_datalen = roundup(decrypted_datalen, blksize); - datablob_len = format_len + 1 + strlen(master_desc) + 1 - + strlen(datalen) + 1 + ivsize + 1 + encrypted_datalen; + if (check_add_overflow(format_len + 1 + strlen(master_desc) + 1 + + strlen(datalen) + 1 + ivsize + 1, + encrypted_datalen, &datablob_len)) + return ERR_PTR(-EINVAL); + + if (check_add_overflow(datablob_len, + payload_datalen + HASH_SIZE + 1, + &payload_totallen)) + return ERR_PTR(-EINVAL); - ret = key_payload_reserve(key, payload_datalen + datablob_len - + HASH_SIZE + 1); + ret = key_payload_reserve(key, payload_totallen); if (ret < 0) return ERR_PTR(ret); - epayload = kzalloc(sizeof(*epayload) + payload_datalen + - datablob_len + HASH_SIZE + 1, GFP_KERNEL); + epayload = kzalloc_flex(*epayload, payload_data, payload_totallen, + GFP_KERNEL); if (!epayload) return ERR_PTR(-ENOMEM); diff --git a/security/keys/gc.c b/security/keys/gc.c index 748e83818a76..eda445f815d4 100644 --- a/security/keys/gc.c +++ b/security/keys/gc.c @@ -318,9 +318,7 @@ maybe_resched: if (unlikely(gc_state & KEY_GC_REAPING_DEAD_3)) { kdebug("dead wake"); - smp_mb(); - clear_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); - wake_up_bit(&key_gc_flags, KEY_GC_REAPING_KEYTYPE); + clear_and_wake_up_bit(KEY_GC_REAPING_KEYTYPE, &key_gc_flags); } if (gc_state & KEY_GC_REAP_AGAIN) diff --git a/security/keys/key.c b/security/keys/key.c index b34a64d81d47..6ebb8691551f 100644 --- a/security/keys/key.c +++ b/security/keys/key.c @@ -588,11 +588,18 @@ int key_reject_and_link(struct key *key, ret = -EBUSY; if (keyring) { - if (keyring->restrict_link) - return -EPERM; - link_ret = __key_link_lock(keyring, &key->index_key); if (link_ret == 0) { + /* + * Check the restriction under the keyring semaphore. + * keyring_restrict() installs it while holding the + * semaphore, so testing it beforehand races with a + * concurrent restriction install. + */ + if (keyring->restrict_link) { + __key_link_end(keyring, &key->index_key, edit); + return -EPERM; + } link_ret = __key_link_begin(keyring, &key->index_key, &edit); if (link_ret < 0) __key_link_end(keyring, &key->index_key, edit); @@ -840,9 +847,6 @@ static key_ref_t __key_create_or_update(key_ref_t keyring_ref, key_check(keyring); - if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) - restrict_link = keyring->restrict_link; - key_ref = ERR_PTR(-ENOTDIR); if (keyring->type != &key_type_keyring) goto error_put_type; @@ -880,6 +884,9 @@ static key_ref_t __key_create_or_update(key_ref_t keyring_ref, goto error_link_end; } + if (!(flags & KEY_ALLOC_BYPASS_RESTRICTION)) + restrict_link = keyring->restrict_link; + if (restrict_link && restrict_link->check) { ret = restrict_link->check(keyring, index_key.type, &prep.payload, restrict_link->key); diff --git a/security/keys/keyctl.c b/security/keys/keyctl.c index d14ace88e529..36ffba0ebba6 100644 --- a/security/keys/keyctl.c +++ b/security/keys/keyctl.c @@ -677,12 +677,14 @@ okay: /* calculate how much information we're going to return */ ret = -ENOMEM; + down_read(&key->sem); infobuf = kasprintf(GFP_KERNEL, "%s;%d;%d;%08x;", key->type->name, from_kuid_munged(current_user_ns(), key->uid), from_kgid_munged(current_user_ns(), key->gid), key->perm); + up_read(&key->sem); if (!infobuf) goto error2; infolen = strlen(infobuf); @@ -806,8 +808,12 @@ static long __keyctl_read_key(struct key *key, char *buffer, size_t buflen) down_read(&key->sem); ret = key_validate(key); - if (ret == 0) - ret = key->type->read(key, buffer, buflen); + if (ret == 0) { + if (key->type->read) + ret = key->type->read(key, buffer, buflen); + else + ret = -EOPNOTSUPP; + } up_read(&key->sem); return ret; } @@ -861,11 +867,6 @@ long keyctl_read_key(key_serial_t keyid, char __user *buffer, size_t buflen) /* the key is probably readable - now try to read it */ can_read_key: - if (!key->type->read) { - ret = -EOPNOTSUPP; - goto key_put_out; - } - if (!buffer || !buflen) { /* Get the key length from the read method */ ret = __keyctl_read_key(key, NULL, 0); diff --git a/security/keys/keyring.c b/security/keys/keyring.c index 15bf4af8f282..7143f3079d20 100644 --- a/security/keys/keyring.c +++ b/security/keys/keyring.c @@ -14,6 +14,9 @@ #include <linux/err.h> #include <linux/user_namespace.h> #include <linux/nsproxy.h> +#include <linux/once.h> +#include <linux/random.h> +#include <linux/siphash.h> #include <keys/keyring-type.h> #include <keys/user-type.h> #include <linux/assoc_array_priv.h> @@ -147,49 +150,46 @@ static int keyring_instantiate(struct key *keyring, } /* - * Multiply 64-bits by 32-bits to 96-bits and fold back to 64-bit. Ideally we'd - * fold the carry back too, but that requires inline asm. + * Give key types and domains address-independent identities. Zero means that + * an ID has not yet been allocated. As with inode sequence numbers used by + * futexes, this relies on the 64-bit counter not wrapping in a machine's + * lifetime. */ -static u64 mult_64x32_and_fold(u64 x, u32 y) +static u64 key_index_id(atomic64_t *id) { - u64 hi = (u64)(u32)(x >> 32) * y; - u64 lo = (u64)(u32)(x) * y; - return lo + ((u64)(u32)hi << 32) + (u32)(hi >> 32); + static atomic64_t next_id; + u64 old, new; + + old = atomic64_read(id); + if (likely(old)) + return old; + + do { + new = atomic64_inc_return(&next_id); + } while (WARN_ON_ONCE(!new)); + + old = 0; + if (!atomic64_try_cmpxchg_relaxed(id, &old, new)) + return old; + return new; } /* - * Hash a key type and description. + * Hash a key type, domain and description. KEYCTL_READ exposes the tree's + * traversal order, so no part of the index may contain kernel addresses. */ static void hash_key_type_and_desc(struct keyring_index_key *index_key) { const unsigned level_shift = ASSOC_ARRAY_LEVEL_STEP; const unsigned long fan_mask = ASSOC_ARRAY_FAN_MASK; - const char *description = index_key->description; - unsigned long hash, type; - u32 piece; + static siphash_key_t hash_key __read_mostly; + unsigned long hash; u64 acc; - int n, desc_len = index_key->desc_len; - - type = (unsigned long)index_key->type; - acc = mult_64x32_and_fold(type, desc_len + 13); - acc = mult_64x32_and_fold(acc, 9207); - piece = (unsigned long)index_key->domain_tag; - acc = mult_64x32_and_fold(acc, piece); - acc = mult_64x32_and_fold(acc, 9207); - - for (;;) { - n = desc_len; - if (n <= 0) - break; - if (n > 4) - n = 4; - piece = 0; - memcpy(&piece, description, n); - description += n; - desc_len -= n; - acc = mult_64x32_and_fold(acc, piece); - acc = mult_64x32_and_fold(acc, 9207); - } + + get_random_once(&hash_key, sizeof(hash_key)); + acc = siphash(index_key->description, index_key->desc_len, &hash_key); + acc = siphash_3u64(index_key->type_id, index_key->domain_id, acc, + &hash_key); /* Fold the hash down to 32 bits if need be. */ hash = acc; @@ -209,7 +209,7 @@ static void hash_key_type_and_desc(struct keyring_index_key *index_key) /* * Finalise an index key to include a part of the description actually in the - * index key, to set the domain tag and to calculate the hash. + * index key, to set the domain tag and index IDs, and to calculate the hash. */ void key_set_index_key(struct keyring_index_key *index_key) { @@ -225,6 +225,14 @@ void key_set_index_key(struct keyring_index_key *index_key) index_key->domain_tag = &default_domain_tag; } + /* Preserve the IDs when copying an existing key's index. In + * particular, its type may already have been unregistered. + */ + if (!index_key->type_id) + index_key->type_id = key_index_id(&index_key->type->index_id); + if (!index_key->domain_id) + index_key->domain_id = key_index_id(&index_key->domain_tag->index_id); + hash_key_type_and_desc(index_key); } @@ -263,15 +271,19 @@ void key_remove_domain(struct key_tag *domain_tag) /* * Build the next index key chunk. * + * The hash and inline description are followed by the 64-bit type and domain + * IDs, low word first, and then by the rest of the description. * We return it one word-sized chunk at a time. */ static unsigned long keyring_get_key_chunk(const void *data, int level) { + const unsigned int id_chunks = sizeof(u64) / sizeof(unsigned long); const struct keyring_index_key *index_key = data; + int desc_len = index_key->desc_len; unsigned long chunk = 0; - const u8 *d; - int desc_len = index_key->desc_len, n = sizeof(chunk); + int n = sizeof(chunk); unsigned int offset; + const u8 *d; level /= ASSOC_ARRAY_KEY_CHUNK_SIZE; switch (level) { @@ -279,27 +291,30 @@ static unsigned long keyring_get_key_chunk(const void *data, int level) return index_key->hash; case 1: return index_key->x; - case 2: - return (unsigned long)index_key->type; - case 3: - return (unsigned long)index_key->domain_tag; - default: - level -= 4; - offset = sizeof(index_key->desc) + level * sizeof(long); - if (desc_len <= offset) - return 0; - - d = index_key->description + offset; - desc_len -= offset; - if (desc_len > n) - desc_len = n; - d += desc_len; - do { - chunk <<= 8; - chunk |= *--d; - } while (--desc_len > 0); - return chunk; } + + level -= 2; + if (level < id_chunks) + return index_key->type_id >> (level * ASSOC_ARRAY_KEY_CHUNK_SIZE); + level -= id_chunks; + if (level < id_chunks) + return index_key->domain_id >> (level * ASSOC_ARRAY_KEY_CHUNK_SIZE); + level -= id_chunks; + + offset = sizeof(index_key->desc) + level * sizeof(long); + if (desc_len <= offset) + return 0; + + d = index_key->description + offset; + desc_len -= offset; + if (desc_len > n) + desc_len = n; + d += desc_len; + do { + chunk <<= 8; + chunk |= *--d; + } while (--desc_len > 0); + return chunk; } static unsigned long keyring_get_object_key_chunk(const void *object, int level) @@ -331,6 +346,7 @@ static int keyring_diff_objects(const void *object, const void *data) const struct keyring_index_key *b = data; unsigned long seg_a, seg_b; int level, i; + u64 diff; level = 0; seg_a = a->hash; @@ -339,28 +355,22 @@ static int keyring_diff_objects(const void *object, const void *data) goto differ; level += ASSOC_ARRAY_KEY_CHUNK_SIZE / 8; - /* The number of bits contributed by the hash is controlled by a - * constant in the assoc_array headers. Everything else thereafter we - * can deal with as being machine word-size dependent. - */ + /* The inline description contributes one machine-word-sized chunk. */ seg_a = a->x; seg_b = b->x; if ((seg_a ^ seg_b) != 0) goto differ; level += sizeof(unsigned long); - /* The next bit may not work on big endian */ - seg_a = (unsigned long)a->type; - seg_b = (unsigned long)b->type; - if ((seg_a ^ seg_b) != 0) - goto differ; - level += sizeof(unsigned long); + diff = a->type_id ^ b->type_id; + if (diff) + return level * 8 + __ffs64(diff); + level += sizeof(u64); - seg_a = (unsigned long)a->domain_tag; - seg_b = (unsigned long)b->domain_tag; - if ((seg_a ^ seg_b) != 0) - goto differ; - level += sizeof(unsigned long); + diff = a->domain_id ^ b->domain_id; + if (diff) + return level * 8 + __ffs64(diff); + level += sizeof(u64); i = sizeof(a->desc); if (a->desc_len <= i) @@ -1158,7 +1168,7 @@ struct key *find_keyring_by_name(const char *name, bool uid_keyring) * grants Search permission and that hasn't been revoked */ list_for_each_entry(keyring, &ns->keyring_name_list, name_link) { - if (!kuid_has_mapping(ns, keyring->user->uid)) + if (!kuid_has_mapping(ns, keyring->uid)) continue; if (test_bit(KEY_FLAG_REVOKED, &keyring->flags)) diff --git a/security/keys/persistent.c b/security/keys/persistent.c index 97af230aa4b2..bdab99539326 100644 --- a/security/keys/persistent.c +++ b/security/keys/persistent.c @@ -33,25 +33,31 @@ static int key_create_persistent_register(struct user_namespace *ns) } /* - * Create the persistent keyring for the specified user. + * Get or create the persistent keyring for the specified user. * * Called with the namespace's sem locked for writing. + * + * Returns a boolean indicating whether the keyring already existed, + * or a negative error. On success, *persistent_ref holds a reference + * to the keyring. */ -static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, - struct keyring_index_key *index_key) +static int key_get_or_create_persistent(struct user_namespace *ns, kuid_t uid, + struct keyring_index_key *index_key, + key_ref_t *persistent_ref) { struct key *persistent; - key_ref_t reg_ref, persistent_ref; + key_ref_t reg_ref; if (!ns->persistent_keyring_register) { - long err = key_create_persistent_register(ns); + int err = key_create_persistent_register(ns); + if (err < 0) - return ERR_PTR(err); + return err; } else { reg_ref = make_key_ref(ns->persistent_keyring_register, true); - persistent_ref = find_key_to_update(reg_ref, index_key); - if (persistent_ref) - return persistent_ref; + *persistent_ref = find_key_to_update(reg_ref, index_key); + if (*persistent_ref) + return 1; } persistent = keyring_alloc(index_key->description, @@ -61,9 +67,10 @@ static key_ref_t key_create_persistent(struct user_namespace *ns, kuid_t uid, KEY_ALLOC_NOT_IN_QUOTA, NULL, ns->persistent_keyring_register); if (IS_ERR(persistent)) - return ERR_CAST(persistent); + return PTR_ERR(persistent); - return make_key_ref(persistent, true); + *persistent_ref = make_key_ref(persistent, true); + return 0; } /* @@ -78,6 +85,7 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid, key_ref_t reg_ref, persistent_ref; char buf[32]; long ret; + bool created = false; /* Look in the register if it exists */ memset(&index_key, 0, sizeof(index_key)); @@ -100,23 +108,24 @@ static long key_get_persistent(struct user_namespace *ns, kuid_t uid, * also need to create the register. */ down_write(&ns->keyring_sem); - persistent_ref = key_create_persistent(ns, uid, &index_key); + ret = key_get_or_create_persistent(ns, uid, &index_key, + &persistent_ref); up_write(&ns->keyring_sem); - if (!IS_ERR(persistent_ref)) - goto found; - - return PTR_ERR(persistent_ref); + if (ret < 0) + return ret; + created = ret == 0; found: + persistent = key_ref_to_ptr(persistent_ref); ret = key_task_permission(persistent_ref, current_cred(), KEY_NEED_LINK); - if (ret == 0) { - persistent = key_ref_to_ptr(persistent_ref); + if (ret == 0) ret = key_link(key_ref_to_ptr(dest_ref), persistent); - if (ret == 0) { - key_set_timeout(persistent, persistent_keyring_expiry); - ret = persistent->serial; - } - } + + if (ret == 0 || created) + key_set_timeout(persistent, persistent_keyring_expiry); + + if (ret == 0) + ret = persistent->serial; key_ref_put(persistent_ref); return ret; diff --git a/security/keys/proc.c b/security/keys/proc.c index 4f4e2c1824f1..aea06a74d0fe 100644 --- a/security/keys/proc.c +++ b/security/keys/proc.c @@ -68,7 +68,7 @@ static struct rb_node *key_serial_next(struct seq_file *p, struct rb_node *n) n = rb_next(n); while (n) { struct key *key = rb_entry(n, struct key, serial_node); - if (kuid_has_mapping(user_ns, key->user->uid)) + if (kuid_has_mapping(user_ns, key->uid)) break; n = rb_next(n); } @@ -100,7 +100,7 @@ static struct key *find_ge_key(struct seq_file *p, key_serial_t id) return NULL; for (;;) { - if (kuid_has_mapping(user_ns, minkey->user->uid)) + if (kuid_has_mapping(user_ns, minkey->uid)) return minkey; n = rb_next(&minkey->serial_node); if (!n) diff --git a/security/keys/process_keys.c b/security/keys/process_keys.c index a63c46bb2d14..1a51a36ce32a 100644 --- a/security/keys/process_keys.c +++ b/security/keys/process_keys.c @@ -528,7 +528,7 @@ found: /* * Search the process keyrings attached to the supplied cred for the first - * matching key in the manner of search_my_process_keyrings(), but also search + * matching key in the manner of search_cred_keyrings_rcu(), but also search * the keys attached to the assumed authorisation key using its credentials if * one is available. * @@ -556,9 +556,8 @@ key_ref_t search_process_keyrings_rcu(struct keyring_search_context *ctx) ) { const struct cred *cred = ctx->cred; - if (key_validate(cred->request_key_auth) == 0) { - rka = ctx->cred->request_key_auth->payload.data[0]; - + rka = dereference_key_rcu(cred->request_key_auth); + if (rka && key_validate(cred->request_key_auth) == 0) { //// was search_process_keyrings() [ie. recursive] ctx->cred = rka->cred; key_ref = search_cred_keyrings_rcu(ctx); diff --git a/security/keys/request_key.c b/security/keys/request_key.c index fa2bb9f2f538..e6ba2d054399 100644 --- a/security/keys/request_key.c +++ b/security/keys/request_key.c @@ -9,7 +9,7 @@ #include <linux/export.h> #include <linux/sched.h> -#include <linux/kmod.h> +#include <linux/umh.h> #include <linux/err.h> #include <linux/keyctl.h> #include <linux/slab.h> diff --git a/security/keys/request_key_auth.c b/security/keys/request_key_auth.c index 282e09d8fa46..ed6f55b9cdd9 100644 --- a/security/keys/request_key_auth.c +++ b/security/keys/request_key_auth.c @@ -9,6 +9,8 @@ #include <linux/sched.h> #include <linux/err.h> +#include <linux/pid.h> +#include <linux/proc_fs.h> #include <linux/seq_file.h> #include <linux/slab.h> #include <linux/uaccess.h> @@ -73,7 +75,10 @@ static void request_key_auth_describe(const struct key *key, seq_puts(m, "key:"); seq_puts(m, key->description); if (key_is_positive(key)) - seq_printf(m, " pid:%d ci:%zu", rka->pid, rka->callout_len); + seq_printf(m, " pid:%d ci:%zu", + pid_nr_ns(rka->pid, + proc_pid_ns(file_inode(m->file)->i_sb)), + rka->callout_len); } /* @@ -113,6 +118,7 @@ static void free_request_key_auth(struct request_key_auth *rka) if (rka->cred) put_cred(rka->cred); kfree(rka->callout_info); + put_pid(rka->pid); kfree(rka); } @@ -226,14 +232,14 @@ struct key *request_key_auth_new(struct key *target, const char *op, irka = cred->request_key_auth->payload.data[0]; rka->cred = get_cred(irka->cred); - rka->pid = irka->pid; + rka->pid = get_pid(irka->pid); up_read(&cred->request_key_auth->sem); } else { /* it isn't - use this process as the context */ rka->cred = get_cred(cred); - rka->pid = current->pid; + rka->pid = get_pid(task_pid(current)); } rka->target_key = key_get(target); diff --git a/security/keys/trusted-keys/trusted_tpm1.c b/security/keys/trusted-keys/trusted_tpm1.c index bf0bf7f36970..3b7af11d30c4 100644 --- a/security/keys/trusted-keys/trusted_tpm1.c +++ b/security/keys/trusted-keys/trusted_tpm1.c @@ -48,15 +48,17 @@ enum { #ifdef CONFIG_TRUSTED_KEYS_DEBUG static inline void dump_options(struct trusted_key_options *o) { + struct trusted_key_tpm *private = o->private; + if (!trusted_debug) return; pr_debug("sealing key type %d\n", o->keytype); - pr_debug("sealing key handle %0X\n", o->keyhandle); - pr_debug("pcrlock %d\n", o->pcrlock); - pr_debug("pcrinfo %d\n", o->pcrinfo_len); + pr_debug("sealing key handle %0X\n", private->keyhandle); + pr_debug("pcrlock %d\n", private->pcrlock); + pr_debug("pcrinfo %d\n", private->pcrinfo_len); print_hex_dump_debug("pcrinfo ", DUMP_PREFIX_NONE, - 16, 1, o->pcrinfo, o->pcrinfo_len, 0); + 16, 1, private->pcrinfo, private->pcrinfo_len, 0); } static inline void dump_sess(struct osapsess *s) @@ -101,7 +103,7 @@ static inline void dump_tpm_buf(unsigned char *buf) static int TSS_rawhmac(unsigned char *digest, const unsigned char *key, unsigned int keylen, ...) { - struct hmac_sha1_ctx hmac_ctx; + struct hmac_sha1_ctx hmac_ctx __cleanup(hmac_sha1_zeroize_ctx); va_list argp; unsigned int dlen; unsigned char *data; @@ -626,6 +628,7 @@ static int tpm_unseal(struct tpm_buf *tb, static int key_seal(struct trusted_key_payload *p, struct trusted_key_options *o) { + struct trusted_key_tpm *private = o->private; int ret; struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); @@ -637,9 +640,10 @@ static int key_seal(struct trusted_key_payload *p, /* include migratable flag at end of sealed key */ p->key[p->key_len] = p->migratable; - ret = tpm_seal(tb, o->keytype, o->keyhandle, o->keyauth, + ret = tpm_seal(tb, o->keytype, private->keyhandle, private->keyauth, p->key, p->key_len + 1, p->blob, &p->blob_len, - o->blobauth, o->pcrinfo, o->pcrinfo_len); + private->blobauth, private->pcrinfo, + private->pcrinfo_len); if (ret < 0) pr_info("srkseal failed (%d)\n", ret); @@ -652,6 +656,7 @@ static int key_seal(struct trusted_key_payload *p, static int key_unseal(struct trusted_key_payload *p, struct trusted_key_options *o) { + struct trusted_key_tpm *private = o->private; int ret; struct tpm_buf *tb __free(kfree) = kzalloc(TPM_BUFSIZE, GFP_KERNEL); @@ -660,8 +665,8 @@ static int key_unseal(struct trusted_key_payload *p, tpm_buf_init(tb, TPM_BUFSIZE); - ret = tpm_unseal(tb, o->keyhandle, o->keyauth, p->blob, p->blob_len, - o->blobauth, p->key, &p->key_len); + ret = tpm_unseal(tb, private->keyhandle, private->keyauth, p->blob, + p->blob_len, private->blobauth, p->key, &p->key_len); if (ret < 0) pr_info("srkunseal failed (%d)\n", ret); else @@ -695,8 +700,9 @@ static const match_table_t key_tokens = { /* can have zero or more token= options */ static int getoptions(char *c, struct trusted_key_payload *pay, - struct trusted_key_options *opt) + struct trusted_key_options *opt, bool is_tpm2) { + struct trusted_key_tpm *private = opt->private; substring_t args[MAX_OPT_ARGS]; char *p = c; int token; @@ -706,13 +712,8 @@ static int getoptions(char *c, struct trusted_key_payload *pay, unsigned long token_mask = 0; unsigned int digest_len; int i; - int tpm2; - - tpm2 = tpm_is_tpm2(chip); - if (tpm2 < 0) - return tpm2; - opt->hash = tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1; + private->hash = is_tpm2 ? HASH_ALGO_SHA256 : HASH_ALGO_SHA1; if (!c) return 0; @@ -726,11 +727,11 @@ static int getoptions(char *c, struct trusted_key_payload *pay, switch (token) { case Opt_pcrinfo: - opt->pcrinfo_len = strlen(args[0].from) / 2; - if (opt->pcrinfo_len > MAX_PCRINFO_SIZE) + private->pcrinfo_len = strlen(args[0].from) / 2; + if (private->pcrinfo_len > MAX_PCRINFO_SIZE) return -EINVAL; - res = hex2bin(opt->pcrinfo, args[0].from, - opt->pcrinfo_len); + res = hex2bin(private->pcrinfo, args[0].from, + private->pcrinfo_len); if (res < 0) return -EINVAL; break; @@ -739,12 +740,12 @@ static int getoptions(char *c, struct trusted_key_payload *pay, if (res < 0) return -EINVAL; opt->keytype = SEAL_keytype; - opt->keyhandle = handle; + private->keyhandle = handle; break; case Opt_keyauth: if (strlen(args[0].from) != 2 * SHA1_DIGEST_SIZE) return -EINVAL; - res = hex2bin(opt->keyauth, args[0].from, + res = hex2bin(private->keyauth, args[0].from, SHA1_DIGEST_SIZE); if (res < 0) return -EINVAL; @@ -755,21 +756,23 @@ static int getoptions(char *c, struct trusted_key_payload *pay, * hex strings. TPM 2.0 authorizations are simple * passwords (although it can take a hash as well) */ - opt->blobauth_len = strlen(args[0].from); + private->blobauth_len = strlen(args[0].from); - if (opt->blobauth_len == 2 * TPM_DIGEST_SIZE) { - res = hex2bin(opt->blobauth, args[0].from, + if (private->blobauth_len == 2 * TPM_DIGEST_SIZE) { + res = hex2bin(private->blobauth, args[0].from, TPM_DIGEST_SIZE); if (res < 0) return -EINVAL; - opt->blobauth_len = TPM_DIGEST_SIZE; + private->blobauth_len = TPM_DIGEST_SIZE; break; } - if (tpm2 && opt->blobauth_len <= sizeof(opt->blobauth)) { - memcpy(opt->blobauth, args[0].from, - opt->blobauth_len); + if (is_tpm2 && + private->blobauth_len <= + sizeof(private->blobauth)) { + memcpy(private->blobauth, args[0].from, + private->blobauth_len); break; } @@ -787,41 +790,42 @@ static int getoptions(char *c, struct trusted_key_payload *pay, res = kstrtoul(args[0].from, 10, &lock); if (res < 0) return -EINVAL; - opt->pcrlock = lock; + private->pcrlock = lock; break; case Opt_hash: if (test_bit(Opt_policydigest, &token_mask)) return -EINVAL; for (i = 0; i < HASH_ALGO__LAST; i++) { if (!strcmp(args[0].from, hash_algo_name[i])) { - opt->hash = i; + private->hash = i; break; } } if (i == HASH_ALGO__LAST) return -EINVAL; - if (!tpm2 && i != HASH_ALGO_SHA1) { + if (!is_tpm2 && i != HASH_ALGO_SHA1) { pr_info("TPM 1.x only supports SHA-1.\n"); return -EINVAL; } break; case Opt_policydigest: - digest_len = hash_digest_size[opt->hash]; - if (!tpm2 || strlen(args[0].from) != (2 * digest_len)) + digest_len = hash_digest_size[private->hash]; + if (!is_tpm2 || + strlen(args[0].from) != (2 * digest_len)) return -EINVAL; - res = hex2bin(opt->policydigest, args[0].from, + res = hex2bin(private->policydigest, args[0].from, digest_len); if (res < 0) return -EINVAL; - opt->policydigest_len = digest_len; + private->policydigest_len = digest_len; break; case Opt_policyhandle: - if (!tpm2) + if (!is_tpm2) return -EINVAL; res = kstrtoul(args[0].from, 16, &handle); if (res < 0) return -EINVAL; - opt->policyhandle = handle; + private->policyhandle = handle; break; default: return -EINVAL; @@ -830,29 +834,33 @@ static int getoptions(char *c, struct trusted_key_payload *pay, return 0; } -static struct trusted_key_options *trusted_options_alloc(void) +static struct trusted_key_options *trusted_options_alloc(bool is_tpm2) { + struct trusted_key_tpm *private; struct trusted_key_options *options; - int tpm2; - - tpm2 = tpm_is_tpm2(chip); - if (tpm2 < 0) - return NULL; options = kzalloc_obj(*options); if (options) { /* set any non-zero defaults */ options->keytype = SRK_keytype; - if (!tpm2) - options->keyhandle = SRKHANDLE; + private = kzalloc_obj(*private); + if (!private) { + kfree_sensitive(options); + options = NULL; + } else { + if (!is_tpm2) + private->keyhandle = SRKHANDLE; + options->private = private; + } } return options; } static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob) { - struct trusted_key_options *options = NULL; + struct trusted_key_options *options __free(kfree_sensitive) = NULL; + struct trusted_key_tpm *private __free(kfree_sensitive) = NULL; int ret = 0; int tpm2; @@ -860,19 +868,19 @@ static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob) if (tpm2 < 0) return tpm2; - options = trusted_options_alloc(); + options = trusted_options_alloc(tpm2); if (!options) return -ENOMEM; - ret = getoptions(datablob, p, options); + private = options->private; + + ret = getoptions(datablob, p, options, tpm2); if (ret < 0) - goto out; + return ret; dump_options(options); - if (!options->keyhandle && !tpm2) { - ret = -EINVAL; - goto out; - } + if (!private->keyhandle && !tpm2) + return -EINVAL; if (tpm2) ret = tpm2_seal_trusted(chip, p, options); @@ -880,24 +888,24 @@ static int trusted_tpm_seal(struct trusted_key_payload *p, char *datablob) ret = key_seal(p, options); if (ret < 0) { pr_info("key_seal failed (%d)\n", ret); - goto out; + return ret; } - if (options->pcrlock) { - ret = pcrlock(options->pcrlock); + if (private->pcrlock) { + ret = pcrlock(private->pcrlock); if (ret < 0) { pr_info("pcrlock failed (%d)\n", ret); - goto out; + return ret; } } -out: - kfree_sensitive(options); + return ret; } static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob) { - struct trusted_key_options *options = NULL; + struct trusted_key_options *options __free(kfree_sensitive) = NULL; + struct trusted_key_tpm *private __free(kfree_sensitive) = NULL; int ret = 0; int tpm2; @@ -905,36 +913,36 @@ static int trusted_tpm_unseal(struct trusted_key_payload *p, char *datablob) if (tpm2 < 0) return tpm2; - options = trusted_options_alloc(); + options = trusted_options_alloc(tpm2); if (!options) return -ENOMEM; + private = options->private; - ret = getoptions(datablob, p, options); + ret = getoptions(datablob, p, options, tpm2); if (ret < 0) - goto out; + return ret; dump_options(options); - if (!options->keyhandle && !tpm2) { - ret = -EINVAL; - goto out; - } + if (!private->keyhandle && !tpm2) + return -EINVAL; if (tpm2) ret = tpm2_unseal_trusted(chip, p, options); else ret = key_unseal(p, options); - if (ret < 0) + if (ret < 0) { pr_info("key_unseal failed (%d)\n", ret); + return ret; + } - if (options->pcrlock) { - ret = pcrlock(options->pcrlock); + if (private->pcrlock) { + ret = pcrlock(private->pcrlock); if (ret < 0) { pr_info("pcrlock failed (%d)\n", ret); - goto out; + return ret; } } -out: - kfree_sensitive(options); + return ret; } diff --git a/security/keys/trusted-keys/trusted_tpm2.c b/security/keys/trusted-keys/trusted_tpm2.c index 67225dd562a9..30188a0ec676 100644 --- a/security/keys/trusted-keys/trusted_tpm2.c +++ b/security/keys/trusted-keys/trusted_tpm2.c @@ -23,36 +23,48 @@ static int tpm2_key_encode(struct trusted_key_payload *payload, struct trusted_key_options *options, u8 *src, u32 len) { + struct trusted_key_tpm *private = options->private; const int SCRATCH_SIZE = PAGE_SIZE; - u8 *scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL); - u8 *work = scratch, *work1; - u8 *end_work = scratch + SCRATCH_SIZE; + u8 *scratch __free(kfree) = NULL; + u8 *work, *work1; + u8 *end_work; u8 *priv, *pub; - u16 priv_len, pub_len; + u32 priv_len, pub_len; int ret; + if (len < 4) + return -EINVAL; + priv_len = get_unaligned_be16(src) + 2; - priv = src; + if (priv_len + 2 > len) + return -EIO; + priv = src; src += priv_len; pub_len = get_unaligned_be16(src) + 2; + if (pub_len + priv_len > len) + return -EIO; + pub = src; + scratch = kmalloc(SCRATCH_SIZE, GFP_KERNEL); if (!scratch) return -ENOMEM; + work = scratch; + end_work = scratch + SCRATCH_SIZE; + work = asn1_encode_oid(work, end_work, tpm2key_oid, asn1_oid_len(tpm2key_oid)); - if (options->blobauth_len == 0) { + if (private->blobauth_len == 0) { unsigned char bool[3], *w = bool; /* tag 0 is emptyAuth */ w = asn1_encode_boolean(w, w + sizeof(bool), true); - if (WARN(IS_ERR(w), "BUG: Boolean failed to encode")) { - ret = PTR_ERR(w); - goto err; - } + if (WARN(IS_ERR(w), "BUG: Boolean failed to encode")) + return PTR_ERR(w); + work = asn1_encode_tag(work, end_work, 0, bool, w - bool); } @@ -64,11 +76,10 @@ static int tpm2_key_encode(struct trusted_key_payload *payload, */ if (WARN(work - scratch + pub_len + priv_len + 14 > SCRATCH_SIZE, "BUG: scratch buffer is too small")) { - ret = -EINVAL; - goto err; + return -EINVAL; } - work = asn1_encode_integer(work, end_work, options->keyhandle); + work = asn1_encode_integer(work, end_work, private->keyhandle); work = asn1_encode_octet_string(work, end_work, pub, pub_len); work = asn1_encode_octet_string(work, end_work, priv, priv_len); @@ -78,15 +89,10 @@ static int tpm2_key_encode(struct trusted_key_payload *payload, if (IS_ERR(work1)) { ret = PTR_ERR(work1); pr_err("BUG: ASN.1 encoder failed with %d\n", ret); - goto err; + return ret; } - kfree(scratch); return work1 - payload->blob; - -err: - kfree(scratch); - return ret; } struct tpm2_key_context { @@ -99,8 +105,9 @@ struct tpm2_key_context { static int tpm2_key_decode(struct trusted_key_payload *payload, struct trusted_key_options *options, - u8 **buf) + u8 **buf, unsigned int *blob_len) { + struct trusted_key_tpm *private = options->private; int ret; struct tpm2_key_context ctx; u8 *blob; @@ -115,12 +122,13 @@ static int tpm2_key_decode(struct trusted_key_payload *payload, if (ctx.priv_len + ctx.pub_len > MAX_BLOB_SIZE) return -EINVAL; - blob = kmalloc(ctx.priv_len + ctx.pub_len + 4, GFP_KERNEL); + blob = kmalloc(ctx.priv_len + ctx.pub_len, GFP_KERNEL); if (!blob) return -ENOMEM; *buf = blob; - options->keyhandle = ctx.parent; + *blob_len = ctx.priv_len + ctx.pub_len; + private->keyhandle = ctx.parent; memcpy(blob, ctx.priv, ctx.priv_len); blob += ctx.priv_len; @@ -232,6 +240,7 @@ int tpm2_seal_trusted(struct tpm_chip *chip, struct trusted_key_payload *payload, struct trusted_key_options *options) { + struct trusted_key_tpm *private = options->private; off_t offset = TPM_HEADER_SIZE; struct tpm_buf *buf __free(kfree) = NULL; struct tpm_buf *sized __free(kfree) = NULL; @@ -240,11 +249,11 @@ int tpm2_seal_trusted(struct tpm_chip *chip, u32 flags; int rc; - hash = tpm2_find_hash_alg(options->hash); + hash = tpm2_find_hash_alg(private->hash); if (hash < 0) return hash; - if (!options->keyhandle) + if (!private->keyhandle) return -EINVAL; rc = tpm_try_get_ops(chip); @@ -274,18 +283,18 @@ int tpm2_seal_trusted(struct tpm_chip *chip, tpm_buf_init_sized(sized, TPM_BUFSIZE); - rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); + rc = tpm_buf_append_name(chip, buf, private->keyhandle, NULL); if (rc) goto out; tpm_buf_append_hmac_session(chip, buf, TPM2_SA_DECRYPT, - options->keyauth, TPM_DIGEST_SIZE); + private->keyauth, TPM_DIGEST_SIZE); /* sensitive */ - tpm_buf_append_u16(sized, options->blobauth_len); + tpm_buf_append_u16(sized, private->blobauth_len); - if (options->blobauth_len) - tpm_buf_append(sized, options->blobauth, options->blobauth_len); + if (private->blobauth_len) + tpm_buf_append(sized, private->blobauth, private->blobauth_len); tpm_buf_append_u16(sized, payload->key_len); tpm_buf_append(sized, payload->key, payload->key_len); @@ -298,14 +307,15 @@ int tpm2_seal_trusted(struct tpm_chip *chip, /* key properties */ flags = 0; - flags |= options->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH; + flags |= private->policydigest_len ? 0 : TPM2_OA_USER_WITH_AUTH; flags |= payload->migratable ? 0 : (TPM2_OA_FIXED_TPM | TPM2_OA_FIXED_PARENT); tpm_buf_append_u32(sized, flags); /* policy */ - tpm_buf_append_u16(sized, options->policydigest_len); - if (options->policydigest_len) - tpm_buf_append(sized, options->policydigest, options->policydigest_len); + tpm_buf_append_u16(sized, private->policydigest_len); + if (private->policydigest_len) + tpm_buf_append(sized, private->policydigest, + private->policydigest_len); /* public parameters */ tpm_buf_append_u16(sized, TPM_ALG_NULL); @@ -335,10 +345,11 @@ int tpm2_seal_trusted(struct tpm_chip *chip, goto out; blob_len = tpm_buf_read_u32(buf, &offset); - if (blob_len > MAX_BLOB_SIZE || buf->flags & TPM_BUF_INVALID) { + if (buf->flags & TPM_BUF_INVALID) { rc = -E2BIG; goto out; } + if (buf->length - offset < blob_len) { rc = -EFAULT; goto out; @@ -376,6 +387,7 @@ static int tpm2_load_cmd(struct tpm_chip *chip, u32 *blob_handle) { u8 *blob_ref __free(kfree) = NULL; + struct trusted_key_tpm *private = options->private; struct tpm_buf *buf __free(kfree) = NULL; unsigned int private_len; unsigned int public_len; @@ -384,10 +396,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip, int rc; u32 attrs; - rc = tpm2_key_decode(payload, options, &blob); + rc = tpm2_key_decode(payload, options, &blob, &blob_len); if (rc) { /* old form */ blob = payload->blob; + blob_len = payload->blob_len; payload->old_format = 1; } else { /* Bind for cleanup: */ @@ -395,21 +408,21 @@ static int tpm2_load_cmd(struct tpm_chip *chip, } /* new format carries keyhandle but old format doesn't */ - if (!options->keyhandle) + if (!private->keyhandle) return -EINVAL; /* must be big enough for at least the two be16 size counts */ - if (payload->blob_len < 4) + if (blob_len < 4) return -EINVAL; private_len = get_unaligned_be16(blob); /* must be big enough for following public_len */ - if (private_len + 2 + 2 > (payload->blob_len)) + if (private_len + 2 + 2 > blob_len) return -E2BIG; public_len = get_unaligned_be16(blob + 2 + private_len); - if (private_len + 2 + public_len + 2 > payload->blob_len) + if (private_len + 2 + public_len + 2 > blob_len) return -E2BIG; pub = blob + 2 + private_len + 2; @@ -439,11 +452,11 @@ static int tpm2_load_cmd(struct tpm_chip *chip, tpm_buf_init(buf, TPM_BUFSIZE); tpm_buf_reset(buf, TPM2_ST_SESSIONS, TPM2_CC_LOAD); - rc = tpm_buf_append_name(chip, buf, options->keyhandle, NULL); + rc = tpm_buf_append_name(chip, buf, private->keyhandle, NULL); if (rc) return rc; - tpm_buf_append_hmac_session(chip, buf, 0, options->keyauth, + tpm_buf_append_hmac_session(chip, buf, 0, private->keyauth, TPM_DIGEST_SIZE); tpm_buf_append(buf, blob, blob_len); @@ -483,6 +496,7 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, struct trusted_key_options *options, u32 blob_handle) { + struct trusted_key_tpm *private = options->private; struct tpm_header *head; struct tpm_buf *buf __free(kfree) = NULL; u16 data_len; @@ -507,10 +521,10 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, if (rc) return rc; - if (!options->policyhandle) { + if (!private->policyhandle) { tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, - options->blobauth, - options->blobauth_len); + private->blobauth, + private->blobauth_len); } else { /* * FIXME: The policy session was generated outside the @@ -523,9 +537,9 @@ static int tpm2_unseal_cmd(struct tpm_chip *chip, * could repeat our actions with the exfiltrated * password. */ - tpm2_buf_append_auth(buf, options->policyhandle, + tpm2_buf_append_auth(buf, private->policyhandle, NULL /* nonce */, 0, 0, - options->blobauth, options->blobauth_len); + private->blobauth, private->blobauth_len); if (tpm2_chip_auth(chip)) { tpm_buf_append_hmac_session(chip, buf, TPM2_SA_ENCRYPT, NULL, 0); diff --git a/security/landlock/.kunitconfig b/security/landlock/.kunitconfig index f9423f01ac5b..fe36228d37ea 100644 --- a/security/landlock/.kunitconfig +++ b/security/landlock/.kunitconfig @@ -1,6 +1,8 @@ CONFIG_AUDIT=y +CONFIG_FTRACE=y CONFIG_KUNIT=y CONFIG_NET=y +CONFIG_SCHED_TRACER=y CONFIG_SECURITY=y CONFIG_SECURITY_LANDLOCK=y CONFIG_SECURITY_LANDLOCK_KUNIT_TEST=y diff --git a/security/landlock/Makefile b/security/landlock/Makefile index 2711f4876939..97cf668db165 100644 --- a/security/landlock/Makefile +++ b/security/landlock/Makefile @@ -9,7 +9,9 @@ landlock-y := \ task.o \ fs.o \ tsync.o \ - domain.o + domain.o \ + ns.o \ + cap.o landlock-$(CONFIG_INET) += net.o diff --git a/security/landlock/access.h b/security/landlock/access.h index bbbb41f41147..f9eaa2f53686 100644 --- a/security/landlock/access.h +++ b/security/landlock/access.h @@ -19,9 +19,9 @@ /* * All access rights that are denied by default whether they are handled or not - * by a ruleset/layer. This must be ORed with all domain->handled_masks[] - * entries when we need to get the absolute handled access masks, see - * landlock_upgrade_handled_access_masks(). + * by a ruleset/layer. This must be ORed with the .handled field of all + * domain->layers[] entries when we need to get the absolute handled access + * masks, see landlock_upgrade_handled_layer_config(). */ /* clang-format off */ #define _LANDLOCK_ACCESS_FS_INITIALLY_DENIED ( \ @@ -42,14 +42,17 @@ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_FS); static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_ACCESS_NET); /* Makes sure all scoped rights can be stored. */ static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_SCOPE); +/* Makes sure all permissions can be stored. */ +static_assert(BITS_PER_TYPE(access_mask_t) >= LANDLOCK_NUM_PERMISSION); /* Makes sure for_each_set_bit() and for_each_clear_bit() calls are OK. */ static_assert(sizeof(unsigned long) >= sizeof(access_mask_t)); -/* Ruleset access masks. */ +/* Access and permission masks (bitfields only). */ struct access_masks { access_mask_t fs : LANDLOCK_NUM_ACCESS_FS; access_mask_t net : LANDLOCK_NUM_ACCESS_NET; access_mask_t scope : LANDLOCK_NUM_SCOPE; + access_mask_t permissions : LANDLOCK_NUM_PERMISSION; } __packed __aligned(sizeof(u32)); union access_masks_all { @@ -62,6 +65,50 @@ static_assert(sizeof(typeof_member(union access_masks_all, masks)) == sizeof(typeof_member(union access_masks_all, all))); /** + * struct permission_masks - Per-permission member bitmasks + */ +struct permission_masks { + /** + * @ns_types: Namespace type member mask, indexed in FOR_EACH_NS_TYPE() + * order. + */ + u64 ns_types : LANDLOCK_NUM_NAMESPACE_TYPE; + /** + * @caps: Capability member mask, indexed by CAP_* values. + */ + u64 caps : LANDLOCK_NUM_CAPABILITY; +} __packed __aligned(sizeof(u64)); + +static_assert(sizeof(struct permission_masks) == sizeof(u64)); +/* All permission_masks bitfields must fit in a single u64. */ +static_assert(LANDLOCK_NUM_CAPABILITY + LANDLOCK_NUM_NAMESPACE_TYPE <= + BITS_PER_TYPE(u64)); + +/** + * struct layer_config - Per-layer access configuration + * + * A ruleset stores one mutable layer and a domain stores a flexible array of + * immutable layers. Unlike filesystem and network access rights, namespace + * types and capabilities use flat bitmasks because their keyspaces are small + * and bounded. + */ +struct layer_config { + /** + * @allowed: Members allowed by each handled permission. + */ + struct permission_masks allowed; + /** + * @handled: Bitmask of access rights and permissions handled (i.e. + * restricted) by this layer. + */ + struct access_masks handled; +}; + +#define _LANDLOCK_LAYER_MASK_PADDING \ + (BITS_PER_TYPE(access_mask_t) - LANDLOCK_NUM_ACCESS_MAX - \ + IS_ENABLED(CONFIG_SECURITY_LANDLOCK_LOG)) + +/** * struct layer_mask - The access rights and rule flags for a layer. * * This has a bit for each access rights and rule flags. During access checks, @@ -81,6 +128,10 @@ struct layer_mask { */ access_mask_t quiet : 1; #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + /** + * @__pad: Padding for the compiler's bitfield initialization. + */ + access_mask_t __pad : _LANDLOCK_LAYER_MASK_PADDING; } __packed __aligned(sizeof(access_mask_t)); /* @@ -123,17 +174,17 @@ static_assert(BITS_PER_TYPE(deny_masks_t) >= static_assert(HWEIGHT(LANDLOCK_MAX_NUM_LAYERS) == 1); /* Upgrades with all initially denied by default access rights. */ -static inline struct access_masks -landlock_upgrade_handled_access_masks(struct access_masks access_masks) +static inline struct layer_config +landlock_upgrade_handled_layer_config(struct layer_config layer_config) { /* * All access rights that are denied by default whether they are * explicitly handled or not. */ - if (access_masks.fs) - access_masks.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED; + if (layer_config.handled.fs) + layer_config.handled.fs |= _LANDLOCK_ACCESS_FS_INITIALLY_DENIED; - return access_masks; + return layer_config; } /* Checks the subset relation between access masks. */ diff --git a/security/landlock/audit.c b/security/landlock/audit.c index e02963834e48..be49ef47b4ea 100644 --- a/security/landlock/audit.c +++ b/security/landlock/audit.c @@ -21,10 +21,10 @@ #include "log.h" /* - * Access-right and scope names are built from the lists shared with the trace - * events (see <linux/landlock.h>). The designated initializer places each name - * at its bit index, so the lookup stays O(1) and does not depend on the entry - * order. log_blockers() adds the "fs."/"net."/"scope." category prefix. + * Access-right, scope, and permission names are built from the lists shared + * with the trace events (see <linux/landlock.h>). The designated initializer + * places each name at its bit index, so the lookup stays O(1) and does not + * depend on the entry order. log_blockers() adds the related category prefix. */ #define _LANDLOCK_NAME_ENTRY(mask, name) [BIT_INDEX(mask)] = name @@ -40,6 +40,12 @@ static const char *const scope_strings[] = { _LANDLOCK_SCOPE_NAMES }; static_assert(ARRAY_SIZE(scope_strings) == LANDLOCK_NUM_SCOPE); +static const char *const permission_strings[] = { + _LANDLOCK_PERMISSION_BLOCKER_NAMES +}; + +static_assert(ARRAY_SIZE(permission_strings) == LANDLOCK_NUM_PERMISSION); + #undef _LANDLOCK_NAME_ENTRY static __attribute_const__ const char * @@ -73,6 +79,12 @@ get_blocker(const enum landlock_request_type type, case LANDLOCK_REQUEST_SCOPE_SIGNAL: WARN_ON_ONCE(access_bit != -1); return scope_strings[BIT_INDEX(LANDLOCK_SCOPE_SIGNAL)]; + + case LANDLOCK_REQUEST_NAMESPACE: + case LANDLOCK_REQUEST_CAPABILITY: + if (WARN_ON_ONCE(access_bit >= ARRAY_SIZE(permission_strings))) + return "unknown"; + return permission_strings[access_bit]; } WARN_ON_ONCE(1); @@ -82,8 +94,8 @@ get_blocker(const enum landlock_request_type type, /* * Returns the audit category prefix prepended to the unprefixed blocker name * returned by get_blocker() (filesystem and network access rights, - * change_topology, and scopes). The ptrace blocker is standalone and carries - * its full name in get_blocker(), so it uses no prefix. + * change_topology, scopes, and permissions). The ptrace blocker is standalone: + * its full name comes from get_blocker(), so it uses no prefix. */ static __attribute_const__ const char * blocker_prefix(const enum landlock_request_type type) @@ -102,6 +114,12 @@ blocker_prefix(const enum landlock_request_type type) case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: case LANDLOCK_REQUEST_SCOPE_SIGNAL: return "scope."; + + case LANDLOCK_REQUEST_NAMESPACE: + return _LANDLOCK_PERMISSION_NAMESPACE_NAME "."; + + case LANDLOCK_REQUEST_CAPABILITY: + return _LANDLOCK_PERMISSION_CAPABILITY_NAME "."; } WARN_ON_ONCE(1); @@ -163,7 +181,7 @@ static void log_domain(struct landlock_hierarchy *const hierarchy) * * @request: Detail of the user space request. * @youngest_denied: The youngest hierarchy node that denied the access. - * @missing: The set of denied access rights. + * @missing: The set of denied access rights or permissions. * @logged: Whether the denial is selected for logging, as computed by * landlock_log_denial() (domain policy and quiet rules). * diff --git a/security/landlock/cap.c b/security/landlock/cap.c new file mode 100644 index 000000000000..b4f491b09baa --- /dev/null +++ b/security/landlock/cap.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Capability hooks + * + * Copyright © 2026 Cloudflare, Inc. + */ + +#include <linux/capability.h> +#include <linux/cred.h> +#include <linux/lsm_audit.h> +#include <linux/lsm_hooks.h> +#include <uapi/linux/landlock.h> + +#include "cap.h" +#include "cred.h" +#include "domain.h" +#include "limits.h" +#include "log.h" +#include "ruleset.h" +#include "setup.h" + +static const struct access_masks cap_permission = { + .permissions = LANDLOCK_PERMISSION_CAPABILITY_USE, +}; + +/** + * hook_capable - Deny capability use for Landlock-sandboxed processes + * + * @cred: Credentials being checked. + * @ns: Target user namespace (intentionally ignored; policy depends on the + * acting credentials and @cap, not namespace ownership). + * @cap: Capability number (CAP_*). + * @opts: Capability check options. CAP_OPT_NOAUDIT skips the denial record: + * no audit record, no trace event, and no denial count. + * + * Pure bitmask check: denies the capability if it is not in the layer's allowed + * set. This hook is purely restrictive: commoncap is registered with + * LSM_ORDER_FIRST so cap_capable() always runs first, which means Landlock can + * deny capabilities that commoncap would allow, but never grant capabilities + * that commoncap denied. + * + * Return: 0 if allowed, -EPERM if capability use is denied. + */ +static int hook_capable(const struct cred *cred, struct user_namespace *ns, + int cap, unsigned int opts) +{ + const struct landlock_cred_security *subject; + size_t denied_layer; + + subject = landlock_get_applicable_subject(cred, cap_permission, NULL); + if (!subject) + return 0; + + denied_layer = landlock_permission_is_denied( + subject->domain, LANDLOCK_PERMISSION_CAPABILITY_USE, + landlock_cap_to_bit(cap)); + if (!denied_layer) + return 0; + + if (!(opts & CAP_OPT_NOAUDIT)) + landlock_log_denial( + subject, + &(struct landlock_request){ + .type = LANDLOCK_REQUEST_CAPABILITY, + .audit.type = LSM_AUDIT_DATA_CAP, + .audit.u.cap = cap, + .permission = + LANDLOCK_PERMISSION_CAPABILITY_USE, + .layer_plus_one = denied_layer, + }); + + return -EPERM; +} + +static struct security_hook_list landlock_hooks[] __ro_after_init = { + LSM_HOOK_INIT(capable, hook_capable), +}; + +__init void landlock_add_cap_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + &landlock_lsmid); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +#include <kunit/test.h> + +static void test_cap_to_bit(struct kunit *const test) +{ + KUNIT_EXPECT_EQ(test, BIT_ULL(0), landlock_cap_to_bit(0)); + KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_NET_RAW), + landlock_cap_to_bit(CAP_NET_RAW)); + KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_SYS_ADMIN), + landlock_cap_to_bit(CAP_SYS_ADMIN)); + KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_LAST_CAP), + landlock_cap_to_bit(CAP_LAST_CAP)); +} + +static void test_cap_to_bit_invalid(struct kunit *const test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + /* clang-format off */ + kunit_warning_suppress(test) { + /* clang-format on */ + KUNIT_EXPECT_EQ(test, 0ULL, landlock_cap_to_bit(-1)); + KUNIT_EXPECT_EQ(test, 0ULL, + landlock_cap_to_bit(CAP_LAST_CAP + 1)); + } +} + +static void test_caps_to_bits_valid(struct kunit *const test) +{ + KUNIT_EXPECT_EQ(test, (u64)CAP_VALID_MASK, + landlock_caps_to_bits(CAP_VALID_MASK)); + KUNIT_EXPECT_EQ(test, BIT_ULL(CAP_NET_RAW), + landlock_caps_to_bits(BIT_ULL(CAP_NET_RAW))); +} + +static void test_caps_to_bits_unknown(struct kunit *const test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + /* clang-format off */ + kunit_warning_suppress(test) { + /* clang-format on */ + KUNIT_EXPECT_EQ( + test, 0ULL, + landlock_caps_to_bits(BIT_ULL(CAP_LAST_CAP + 1))); + } +} + +static void test_caps_to_bits_zero(struct kunit *const test) +{ + KUNIT_EXPECT_EQ(test, 0ULL, landlock_caps_to_bits(0)); +} + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_cap_to_bit), + KUNIT_CASE(test_cap_to_bit_invalid), + KUNIT_CASE(test_caps_to_bits_valid), + KUNIT_CASE(test_caps_to_bits_unknown), + KUNIT_CASE(test_caps_to_bits_zero), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_cap", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/landlock/cap.h b/security/landlock/cap.h new file mode 100644 index 000000000000..11208cbf15d2 --- /dev/null +++ b/security/landlock/cap.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Capability hooks + * + * Copyright © 2026 Cloudflare, Inc. + */ + +#ifndef _SECURITY_LANDLOCK_CAP_H +#define _SECURITY_LANDLOCK_CAP_H + +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/capability.h> +#include <linux/types.h> + +/** + * landlock_cap_to_bit - Convert a capability number to a compact bitmask + * + * @cap: Capability number (CAP_*). + * + * Return: BIT_ULL(@cap), or 0 if @cap is invalid (with a WARN). + */ +static inline u64 landlock_cap_to_bit(const int cap) +{ + if (WARN_ON_ONCE(!cap_valid(cap))) + return 0; + + return BIT_ULL(cap); +} + +/** + * landlock_caps_to_bits - Validate and mask a capability bitmask + * + * @capabilities: Bitmask of capabilities (e.g. from user space). + * + * Return: @capabilities masked to known capabilities. Warns if unknown bits + * are present (callers must pre-mask for user input). + */ +static inline u64 landlock_caps_to_bits(const u64 capabilities) +{ + /* Callers pre-mask (CAP_VALID_MASK); the WARN guards future callers. */ + WARN_ON_ONCE(capabilities & ~CAP_VALID_MASK); + return capabilities & CAP_VALID_MASK; +} + +__init void landlock_add_cap_hooks(void); + +#endif /* _SECURITY_LANDLOCK_CAP_H */ diff --git a/security/landlock/cred.h b/security/landlock/cred.h index a5ff9957949a..1c6a4838f38b 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -138,7 +138,7 @@ landlock_get_applicable_subject(const struct cred *const cred, for (layer_level = domain->num_layers - 1; layer_level >= 0; layer_level--) { union access_masks_all layer = { - .masks = domain->handled_masks[layer_level], + .masks = domain->layers[layer_level].handled, }; if (layer.all & masks_all.all) { diff --git a/security/landlock/domain.c b/security/landlock/domain.c index 93c7104fd6b2..4c0b1f0e1db5 100644 --- a/security/landlock/domain.c +++ b/security/landlock/domain.c @@ -50,7 +50,7 @@ static struct landlock_domain *create_domain(const u32 num_layers) struct landlock_domain *new_domain; build_check_domain(); - new_domain = kzalloc_flex(*new_domain, handled_masks, num_layers, + new_domain = kzalloc_flex(*new_domain, layers, num_layers, GFP_KERNEL_ACCOUNT); if (!new_domain) return ERR_PTR(-ENOMEM); @@ -328,8 +328,8 @@ static int merge_ruleset(struct landlock_domain *const dst, if (WARN_ON_ONCE(dst->num_layers < 1)) return -EINVAL; - dst->handled_masks[dst->num_layers - 1] = - landlock_upgrade_handled_access_masks(src->handled_masks); + dst->layers[dst->num_layers - 1] = + landlock_upgrade_handled_layer_config(src->layer); /* Merges the @src inode tree. */ err = merge_tree(dst, src, LANDLOCK_KEY_INODE); @@ -404,8 +404,8 @@ static int inherit_ruleset(struct landlock_domain *const parent, /* * Copies the parent layer stack and leaves a space for the new layer. */ - memcpy(child->handled_masks, parent->handled_masks, - flex_array_size(parent, handled_masks, parent->num_layers)); + memcpy(child->layers, parent->layers, + flex_array_size(parent, layers, parent->num_layers)); if (WARN_ON_ONCE(!parent->hierarchy)) return -EINVAL; @@ -439,10 +439,11 @@ landlock_merge_ruleset(struct landlock_domain *const parent, int err; might_sleep(); - lockdep_assert_held(&ruleset->lock); if (WARN_ON_ONCE(!ruleset)) return ERR_PTR(-EINVAL); + lockdep_assert_held(&ruleset->lock); + if (parent) { if (parent->num_layers >= LANDLOCK_MAX_NUM_LAYERS) return ERR_PTR(-E2BIG); @@ -478,7 +479,8 @@ landlock_merge_ruleset(struct landlock_domain *const parent, return ERR_PTR(err); #ifdef CONFIG_SECURITY_LANDLOCK_LOG - new_dom->hierarchy->quiet_masks = ruleset->quiet_masks; + new_dom->hierarchy->quiet_access = ruleset->quiet_access; + new_dom->hierarchy->quiet_permission = ruleset->quiet_permission; #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ return no_free_ptr(new_dom); diff --git a/security/landlock/domain.h b/security/landlock/domain.h index 5ce2f91488d5..935e18b12b3c 100644 --- a/security/landlock/domain.h +++ b/security/landlock/domain.h @@ -130,10 +130,15 @@ struct landlock_hierarchy { */ log_new_exec : 1; /** - * @quiet_masks: Bitmasks of access that should be quieted (i.e. not + * @quiet_access: Bitmasks of access that should be quieted (i.e. not * logged) if the related object is marked as quiet. */ - struct access_masks quiet_masks; + struct access_masks quiet_access; + /** + * @quiet_permission: Per-member quiet bitmasks for permission types in + * this layer. + */ + struct permission_masks quiet_permission; #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ }; @@ -217,7 +222,7 @@ struct landlock_domain { * @work_free: Enables to free a domain within a lockless * section. This is only used by landlock_put_domain_deferred() * when @usage reaches zero. The fields @usage, @num_layers and - * @handled_masks are then unused. + * @layers are then unused. */ struct work_struct work_free; struct { @@ -233,17 +238,22 @@ struct landlock_domain { */ u32 num_layers; /** - * @handled_masks: Contains the subset of filesystem and - * network actions that are restricted by a domain. A - * domain saves all layers of merged rulesets in a stack - * (FAM), starting from the first layer to the last one. - * These layers are used when merging rulesets, for user - * space backward compatibility (i.e. future-proof), and - * to properly handle merged rulesets without - * overlapping access rights. These layers are set once - * and never changed for the lifetime of the domain. + * @layers: Per-layer access configuration. A domain + * saves all layers of merged rulesets in a stack (FAM), + * starting from the first layer to the last one. These + * layers are used when merging rulesets, for user space + * backward compatibility (i.e. future-proof), and to + * properly handle merged rulesets without overlapping + * access rights. These layers are set once and never + * changed for the lifetime of the domain. + * + * Not annotated with __counted_by(num_layers): clang + * miscomputes the counter address for a FAM nested in + * an anonymous struct within a union, which trips + * FORTIFY: + * https://github.com/llvm/llvm-project/pull/228309 */ - struct access_masks handled_masks[]; + struct layer_config layers[]; }; }; }; @@ -253,7 +263,7 @@ landlock_get_fs_access_mask(const struct landlock_domain *const domain, const u16 layer_level) { /* Handles all initially denied by default access rights. */ - return domain->handled_masks[layer_level].fs | + return domain->layers[layer_level].handled.fs | _LANDLOCK_ACCESS_FS_INITIALLY_DENIED; } @@ -261,14 +271,14 @@ static inline access_mask_t landlock_get_net_access_mask(const struct landlock_domain *const domain, const u16 layer_level) { - return domain->handled_masks[layer_level].net; + return domain->layers[layer_level].handled.net; } static inline access_mask_t landlock_get_scope_mask(const struct landlock_domain *const domain, const u16 layer_level) { - return domain->handled_masks[layer_level].scope; + return domain->layers[layer_level].handled.scope; } /** @@ -287,7 +297,7 @@ landlock_union_access_masks(const struct landlock_domain *const domain) for (layer_level = 0; layer_level < domain->num_layers; layer_level++) { union access_masks_all layer = { - .masks = domain->handled_masks[layer_level], + .masks = domain->layers[layer_level].handled, }; matches.all |= layer.all; @@ -323,4 +333,63 @@ static inline void landlock_get_domain(struct landlock_domain *const domain) refcount_inc(&domain->usage); } +/** + * landlock_permission_is_denied - Check if a permission request is denied + * + * @domain: The enforced domain. + * @permission_bit: The LANDLOCK_PERMISSION_* flag to check. Must have + * exactly one bit set. + * @request_value: Compact bitmask to look for (e.g. the result of + * landlock_ns_type_to_bit()). Must have exactly one bit set. + * + * Iterate from the youngest layer to the oldest. For each layer that handles + * @permission_bit, check whether @request_value is present in the layer's + * allowed bitmask. Return on the first (youngest) denying layer. + * + * Return: The youngest denying layer + 1, or 0 if allowed. + */ +static inline size_t +landlock_permission_is_denied(const struct landlock_domain *const domain, + const access_mask_t permission_bit, + const u64 request_value) +{ + ssize_t layer; + + BUILD_BUG_ON(sizeof(permission_bit) > sizeof(u32)); + + if (WARN_ON_ONCE(hweight32(permission_bit) != 1) || + WARN_ON_ONCE(hweight64(request_value) != 1)) + return domain->num_layers; + + for (layer = domain->num_layers - 1; layer >= 0; layer--) { + u64 allowed; + + if (!(domain->layers[layer].handled.permissions & + permission_bit)) + continue; + + /* + * Current callers pass only permission types with an explicit + * case below. The default catches a new caller missing its + * member mask. + */ + switch (permission_bit) { + case LANDLOCK_PERMISSION_NAMESPACE_USE: + allowed = domain->layers[layer].allowed.ns_types; + break; + case LANDLOCK_PERMISSION_CAPABILITY_USE: + allowed = domain->layers[layer].allowed.caps; + break; + default: + WARN_ONCE(1, "Unknown permission %u\n", + (unsigned int)permission_bit); + return layer + 1; + } + + if (!(allowed & request_value)) + return layer + 1; + } + return 0; +} + #endif /* _SECURITY_LANDLOCK_DOMAIN_H */ diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 30aa6ce13590..2ab67bcad198 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -61,7 +61,7 @@ static void release_inode(struct landlock_object *const object) __releases(object->lock) { struct inode *const inode = object->underobj; - struct super_block *sb; + atomic_long_t *inode_refs; if (!inode) { spin_unlock(&object->lock); @@ -77,8 +77,8 @@ static void release_inode(struct landlock_object *const object) * Makes sure that if the filesystem is concurrently unmounted, * hook_sb_delete() will wait for us to finish iput(). */ - sb = inode->i_sb; - atomic_long_inc(&landlock_superblock(sb)->inode_refs); + inode_refs = &landlock_superblock(inode->i_sb)->inode_refs; + atomic_long_inc(inode_refs); spin_unlock(&object->lock); /* * Because object->underobj was not NULL, hook_sb_delete() and @@ -92,8 +92,8 @@ static void release_inode(struct landlock_object *const object) */ iput(inode); - if (atomic_long_dec_and_test(&landlock_superblock(sb)->inode_refs)) - wake_up_var(&landlock_superblock(sb)->inode_refs); + if (atomic_long_dec_and_test(inode_refs)) + wake_up_var(inode_refs); } static const struct landlock_object_underops landlock_fs_underops = { @@ -342,7 +342,7 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, /* Transforms relative access rights to absolute ones. */ access_rights |= LANDLOCK_MASK_ACCESS_FS & - ~(ruleset->handled_masks.fs | + ~(ruleset->layer.handled.fs | _LANDLOCK_ACCESS_FS_INITIALLY_DENIED); id.key.object = get_inode_object(d_backing_inode(path->dentry)); if (IS_ERR(id.key.object)) @@ -356,14 +356,14 @@ int landlock_append_fs_rule(struct landlock_ruleset *const ruleset, * held for BTF consistency (enforced by lockdep_assert_held in * TP_fast_assign). */ - if (!err && trace_landlock_add_rule_fs_enabled()) { + if (!err && trace_landlock_add_rule_path_beneath_enabled()) { char *buffer __free(__putname) = __getname(); const char *pathname = buffer ? resolve_path_for_trace(path, buffer) : "<no_mem>"; - trace_landlock_add_rule_fs(ruleset, access_rights, path, - pathname); + trace_landlock_add_rule_path_beneath( + ruleset, flags, access_rights, path, pathname); } mutex_unlock(&ruleset->lock); @@ -423,8 +423,8 @@ static bool unmask_layers_fs(const struct landlock_domain *const domain, ret = landlock_unmask_layers(domain, id, masks, &rule); if (rule) - trace_landlock_check_rule_fs(domain, rule, access_request, - dentry); + trace_landlock_check_rule_inode(domain, rule, access_request, + dentry); return ret; } @@ -1298,11 +1298,12 @@ static int current_check_refer_path(struct dentry *const old_dentry, /* * old_dentry may be the root of the common mount point and * !IS_ROOT(old_dentry) at the same time (e.g. with open_tree() and - * OPEN_TREE_CLONE). We do not need to call dget(old_parent) because - * we keep a reference to old_dentry. + * OPEN_TREE_CLONE). Pin the dentry used as old_parent in either case. + * Otherwise, dget_parent() safely fetches and pins the current parent + * against a concurrent rename(2). */ - old_parent = (old_dentry == mnt_dir.dentry) ? old_dentry : - old_dentry->d_parent; + old_parent = (old_dentry == mnt_dir.dentry) ? dget(old_dentry) : + dget_parent(old_dentry); /* new_dir->dentry is equal to new_dentry->d_parent */ allow_parent1 = collect_domain_accesses(subject->domain, mnt_dir.dentry, @@ -1311,8 +1312,10 @@ static int current_check_refer_path(struct dentry *const old_dentry, allow_parent2 = collect_domain_accesses(subject->domain, mnt_dir.dentry, new_dir->dentry, &layer_masks_parent2); - if (allow_parent1 && allow_parent2) + if (allow_parent1 && allow_parent2) { + dput(old_parent); return 0; + } /* * To be able to compare source and destination domain access rights, @@ -1324,8 +1327,10 @@ static int current_check_refer_path(struct dentry *const old_dentry, subject->domain, &mnt_dir, access_request_parent1, &layer_masks_parent1, &request1, old_dentry, access_request_parent2, &layer_masks_parent2, &request2, - exchange ? new_dentry : NULL)) + exchange ? new_dentry : NULL)) { + dput(old_parent); return 0; + } if (request1.access) { request1.audit.u.path.dentry = old_parent; @@ -1335,6 +1340,7 @@ static int current_check_refer_path(struct dentry *const old_dentry, request2.audit.u.path.dentry = new_dir->dentry; landlock_log_denial(subject, &request2); } + dput(old_parent); /* * This prioritizes EACCES over EXDEV for all actions, including diff --git a/security/landlock/limits.h b/security/landlock/limits.h index 1a7c5fb8f6fd..ddd300e10689 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -11,7 +11,9 @@ #define _SECURITY_LANDLOCK_LIMITS_H #include <linux/bitops.h> +#include <linux/capability.h> #include <linux/limits.h> +#include <linux/ns/ns_common_types.h> #include <uapi/linux/landlock.h> /* clang-format off */ @@ -31,6 +33,13 @@ #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) +#define LANDLOCK_LAST_PERMISSION LANDLOCK_PERMISSION_CAPABILITY_USE +#define LANDLOCK_MASK_PERMISSION ((LANDLOCK_LAST_PERMISSION << 1) - 1) +#define LANDLOCK_NUM_PERMISSION __const_hweight64(LANDLOCK_MASK_PERMISSION) + +#define LANDLOCK_NUM_NAMESPACE_TYPE __const_hweight64((u64)CLONE_NS_ALL) +#define LANDLOCK_NUM_CAPABILITY (CAP_LAST_CAP + 1) + #define LANDLOCK_NUM_ACCESS_MAX \ MAX(MAX(LANDLOCK_NUM_ACCESS_FS, LANDLOCK_NUM_ACCESS_NET), LANDLOCK_NUM_SCOPE) diff --git a/security/landlock/log.c b/security/landlock/log.c index a8578a6f2ce9..43efbb95725c 100644 --- a/security/landlock/log.c +++ b/security/landlock/log.c @@ -12,11 +12,13 @@ #include "access.h" #include "audit.h" +#include "cap.h" #include "common.h" #include "cred.h" #include "domain.h" #include "limits.h" #include "log.h" +#include "ns.h" #include "ruleset.h" #include "trace.h" @@ -382,6 +384,37 @@ static bool is_valid_request(const struct landlock_request *const request) if (WARN_ON_ONCE(!(!!request->layer_plus_one ^ !!request->access))) return false; + if (WARN_ON_ONCE(request->access && request->permission)) + return false; + + switch (request->type) { + case LANDLOCK_REQUEST_NAMESPACE: + if (WARN_ON_ONCE(request->permission != + LANDLOCK_PERMISSION_NAMESPACE_USE) || + WARN_ON_ONCE(request->audit.type != LSM_AUDIT_DATA_NS)) + return false; + break; + case LANDLOCK_REQUEST_CAPABILITY: + if (WARN_ON_ONCE(request->permission != + LANDLOCK_PERMISSION_CAPABILITY_USE) || + WARN_ON_ONCE(request->audit.type != LSM_AUDIT_DATA_CAP)) + return false; + break; + case LANDLOCK_REQUEST_PTRACE: + case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: + case LANDLOCK_REQUEST_FS_ACCESS: + case LANDLOCK_REQUEST_NET_ACCESS: + case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: + case LANDLOCK_REQUEST_SCOPE_SIGNAL: + if (WARN_ON_ONCE(request->permission)) + return false; + break; + default: + WARN_ONCE(1, "Unknown Landlock request type %d\n", + request->type); + return false; + } + if (request->access) { if (WARN_ON_ONCE(!(!!request->layer_masks ^ !!request->all_existing_optional_access))) @@ -436,22 +469,28 @@ is_denial_quieted(const struct landlock_request *const request, if (object_quiet_flag) { const access_mask_t quiet_mask = pick_access_mask_for_request_type( - request->type, youngest_denied->quiet_masks); + request->type, youngest_denied->quiet_access); return (quiet_mask & missing) == missing; } /* - * Either the object is not quiet, or this is a scope request. We check - * request->type to distinguish between the two cases. + * Per-object quieting did not apply. Check request->type for scope and + * permission quieting; ptrace and topology requests are never quiet. */ switch (request->type) { case LANDLOCK_REQUEST_SCOPE_SIGNAL: - return !!(youngest_denied->quiet_masks.scope & + return !!(youngest_denied->quiet_access.scope & LANDLOCK_SCOPE_SIGNAL); case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: - return !!(youngest_denied->quiet_masks.scope & + return !!(youngest_denied->quiet_access.scope & LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); + case LANDLOCK_REQUEST_NAMESPACE: + return !!(youngest_denied->quiet_permission.ns_types & + landlock_ns_type_to_bit(request->audit.u.ns.ns_type)); + case LANDLOCK_REQUEST_CAPABILITY: + return !!(youngest_denied->quiet_permission.caps & + landlock_cap_to_bit(request->audit.u.cap)); /* * Leave LANDLOCK_REQUEST_PTRACE and LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY * unhandled for now - they are never quiet. @@ -508,8 +547,8 @@ void landlock_log_denial(const struct landlock_cred_security *const subject, if (!is_valid_request(request)) return; - missing = request->access; - if (missing) { + missing = request->access ? request->access : request->permission; + if (request->access) { /* Gets the nearest domain that denies the request. */ if (request->layer_masks) { youngest_layer = get_denied_layer(subject->domain, diff --git a/security/landlock/log.h b/security/landlock/log.h index e0a6e44f3ddd..596feeeb7aa6 100644 --- a/security/landlock/log.h +++ b/security/landlock/log.h @@ -15,6 +15,8 @@ struct landlock_cred_security; struct landlock_hierarchy; +struct sockaddr; +struct task_struct; enum landlock_request_type { LANDLOCK_REQUEST_PTRACE = 1, @@ -23,16 +25,49 @@ enum landlock_request_type { LANDLOCK_REQUEST_NET_ACCESS, LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET, LANDLOCK_REQUEST_SCOPE_SIGNAL, + LANDLOCK_REQUEST_NAMESPACE, + LANDLOCK_REQUEST_CAPABILITY, }; +struct landlock_blockers { + /* Blocking access rights or permissions, as selected by @type. */ + access_mask_t access; + enum landlock_request_type type; +}; + +#ifdef CONFIG_TRACEPOINTS + +struct landlock_net_trace { + const struct sockaddr *address; + int addrlen; + u16 socket_family; +}; + +struct landlock_ptrace_trace { + u64 tracee_domain_id; + const struct task_struct *tracer; +}; + +struct landlock_signal_trace { + u64 target_domain_id; + int signal; +}; + +#endif /* CONFIG_TRACEPOINTS */ + /* * We should be careful to only use a variable of this type for * landlock_log_denial(). This way, the compiler can remove it entirely if * CONFIG_SECURITY_LANDLOCK_LOG is not set. */ struct landlock_request { - /* Mandatory fields. */ + /* Mandatory request type. */ enum landlock_request_type type; + + /* Required field for permission requests. */ + access_mask_t permission; + + /* Mandatory audit context. */ struct common_audit_data audit; /** @@ -52,13 +87,24 @@ struct landlock_request { deny_masks_t deny_masks; optional_access_t quiet_optional_accesses; - /* - * Other-party domain ID for a relational (scope/ptrace) denial, or 0 if - * that party is unsandboxed. An ID, not a pointer: the other task can - * replace its credential and free the domain it referenced. Trace path - * only; audit ignores it. - */ - u64 other_domain_id; +#ifdef CONFIG_TRACEPOINTS + union { + /* + * Other-party domain ID for an abstract UNIX socket scope + * denial, or 0 if that party is unsandboxed. Store an ID, not + * a pointer: the other task can replace its credential and free + * the domain it referenced. + */ + u64 other_domain_id; + + /* Synchronous context for a network denial. */ + const struct landlock_net_trace *trace_net; + /* Synchronous context for a ptrace denial. */ + const struct landlock_ptrace_trace *trace_ptrace; + /* Synchronous context for a signal denial. */ + const struct landlock_signal_trace *trace_signal; + }; +#endif /* CONFIG_TRACEPOINTS */ }; #ifdef CONFIG_SECURITY_LANDLOCK_LOG diff --git a/security/landlock/net.c b/security/landlock/net.c index 8f2aaac54b33..8fd73cf4bd15 100644 --- a/security/landlock/net.c +++ b/security/landlock/net.c @@ -35,7 +35,7 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, BUILD_BUG_ON(sizeof(port) > sizeof(id.key.data)); /* Transforms relative access rights to absolute ones. */ - access_rights |= LANDLOCK_MASK_ACCESS_NET & ~ruleset->handled_masks.net; + access_rights |= LANDLOCK_MASK_ACCESS_NET & ~ruleset->layer.handled.net; mutex_lock(&ruleset->lock); err = landlock_insert_rule(ruleset, id, access_rights, flags); @@ -47,7 +47,8 @@ int landlock_append_net_rule(struct landlock_ruleset *const ruleset, * TP_fast_assign). */ if (!err) - trace_landlock_add_rule_net(ruleset, access_rights, port); + trace_landlock_add_rule_net_port(ruleset, flags, access_rights, + port); mutex_unlock(&ruleset->lock); return err; @@ -63,7 +64,7 @@ static bool unmask_layers_net(const struct landlock_domain *const domain, ret = landlock_unmask_layers(domain, id, masks, &rule); if (rule) - trace_landlock_check_rule_net( + trace_landlock_check_rule_net_port( domain, rule, access_request, ntohs((__force __be16)id.key.data)); return ret; @@ -92,7 +93,7 @@ static int current_check_access_socket(struct socket *const sock, return 0; /* Checks for minimal header length to safely read sa_family. */ - if (addrlen < offsetofend(typeof(*address), sa_family)) + if (addrlen < (int)offsetofend(typeof(*address), sa_family)) return -EINVAL; /* @@ -144,6 +145,17 @@ static int current_check_access_socket(struct socket *const sock, .audit.u.net = &audit_net, .access = access_request, .layer_masks = &layer_masks, +#ifdef CONFIG_TRACEPOINTS + .trace_net = + &(struct landlock_net_trace){ + .address = + address, + .addrlen = + addrlen, + .socket_family = + sock_family, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EACCES; } @@ -275,14 +287,22 @@ static int current_check_access_socket(struct socket *const sock, audit_net.family = address->sa_family; audit_net.sk = sock->sk; - landlock_log_denial(subject, - &(struct landlock_request){ - .type = LANDLOCK_REQUEST_NET_ACCESS, - .audit.type = LSM_AUDIT_DATA_NET, - .audit.u.net = &audit_net, - .access = access_request, - .layer_masks = &layer_masks, - }); + landlock_log_denial( + subject, &(struct landlock_request){ + .type = LANDLOCK_REQUEST_NET_ACCESS, + .audit.type = LSM_AUDIT_DATA_NET, + .audit.u.net = &audit_net, + .access = access_request, + .layer_masks = &layer_masks, +#ifdef CONFIG_TRACEPOINTS + .trace_net = + &(struct landlock_net_trace){ + .address = address, + .addrlen = addrlen, + .socket_family = sock_family, + }, +#endif /* CONFIG_TRACEPOINTS */ + }); return -EACCES; } diff --git a/security/landlock/ns.c b/security/landlock/ns.c new file mode 100644 index 000000000000..cd44c927f83a --- /dev/null +++ b/security/landlock/ns.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Landlock - Namespace hooks + * + * Copyright © 2026 Cloudflare, Inc. + */ + +#include <linux/bitops.h> +#include <linux/bug.h> +#include <linux/lsm_audit.h> +#include <linux/lsm_hooks.h> +#include <linux/ns/ns_common_types.h> +#include <linux/ns_common.h> +#include <linux/nsproxy.h> +#include <uapi/linux/landlock.h> + +#include "cred.h" +#include "domain.h" +#include "limits.h" +#include "log.h" +#include "ns.h" +#include "ruleset.h" +#include "setup.h" + +#define _LANDLOCK_NS_TYPE(type, flag) flag, + +static const u32 landlock_namespace_types[] = { + /* clang-format off */ + FOR_EACH_NS_TYPE(_LANDLOCK_NS_TYPE) + /* clang-format on */ +}; + +#undef _LANDLOCK_NS_TYPE + +static_assert(ARRAY_SIZE(landlock_namespace_types) == + LANDLOCK_NUM_NAMESPACE_TYPE); + +/* Ensures the audit ns_id field can hold ns_common.ns_id without truncation. */ +static_assert(sizeof(((struct common_audit_data *)NULL)->u.ns.ns_id) >= + sizeof(((struct ns_common *)NULL)->ns_id)); + +/** + * landlock_ns_type_to_bit - Convert a namespace type to a compact bitmask + * + * @ns_type: Namespace type (``CLONE_NEW*``). + * + * Return: The compact bit for @ns_type, or 0 if @ns_type is invalid (with a + * warning). + */ +u64 landlock_ns_type_to_bit(const u32 ns_type) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) { + if (landlock_namespace_types[i] == ns_type) + return BIT_ULL(i); + } + + WARN_ONCE(1, "Unknown namespace type 0x%x\n", ns_type); + return 0; +} + +/** + * landlock_ns_types_to_bits - Convert namespace types to a compact bitmask + * + * @ns_types: Bitmask of namespace types (``CLONE_NEW*``). + * + * Return: The compact bits for all known @ns_types. Warns if unknown bits are + * present (callers must pre-mask user input). + */ +u64 landlock_ns_types_to_bits(const u64 ns_types) +{ + u64 bits = 0; + size_t i; + + /* Callers pre-mask (CLONE_NS_ALL); the WARN guards future callers. */ + WARN_ON_ONCE(ns_types & ~(u64)CLONE_NS_ALL); + for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) { + if (ns_types & landlock_namespace_types[i]) + bits |= BIT_ULL(i); + } + return bits; +} + +static const struct access_masks ns_permission = { + .permissions = LANDLOCK_PERMISSION_NAMESPACE_USE, +}; + +/** + * check_ns_type - Check namespace use permission + * + * @ns: The namespace being allocated or installed. + * + * Shared check for namespace_init (creation via clone(2), unshare(2), + * open_tree(2), or fsmount(2)) and namespace_install (use via setns(2)): denies + * when the namespace type is not in the domain's allowed set. At allocation + * time @ns->ns_id is still zero and is logged as such. + * + * Return: 0 if allowed, -EPERM if denied. + */ +static int check_ns_type(struct ns_common *const ns) +{ + const struct landlock_cred_security *subject; + size_t denied_layer; + + subject = landlock_get_applicable_subject(current_cred(), ns_permission, + NULL); + if (!subject) + return 0; + + denied_layer = landlock_permission_is_denied( + subject->domain, LANDLOCK_PERMISSION_NAMESPACE_USE, + landlock_ns_type_to_bit(ns->ns_type)); + if (!denied_layer) + return 0; + + landlock_log_denial(subject, + &(struct landlock_request){ + .type = LANDLOCK_REQUEST_NAMESPACE, + .audit.type = LSM_AUDIT_DATA_NS, + .permission = + LANDLOCK_PERMISSION_NAMESPACE_USE, + .audit.u.ns.ns_type = ns->ns_type, + .audit.u.ns.ns_id = ns->ns_id, + .layer_plus_one = denied_layer, + }); + return -EPERM; +} + +static int hook_namespace_init(struct ns_common *const ns) +{ + return check_ns_type(ns); +} + +static int hook_namespace_install(const struct nsset *const nsset, + struct ns_common *const ns) +{ + return check_ns_type(ns); +} + +static struct security_hook_list landlock_hooks[] __ro_after_init = { + LSM_HOOK_INIT(namespace_init, hook_namespace_init), + LSM_HOOK_INIT(namespace_install, hook_namespace_install), +}; + +__init void landlock_add_ns_hooks(void) +{ + security_add_hooks(landlock_hooks, ARRAY_SIZE(landlock_hooks), + &landlock_lsmid); +} + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +#include <kunit/test.h> + +static void test_ns_type_to_bit(struct kunit *const test) +{ + u64 seen = 0; + size_t i; + + for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) { + const u64 bit = + landlock_ns_type_to_bit(landlock_namespace_types[i]); + + KUNIT_EXPECT_NE(test, 0ULL, bit); + KUNIT_EXPECT_EQ(test, 0ULL, seen & bit); + seen |= bit; + } + + KUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_NAMESPACE_TYPE - 1, 0), + seen); +} + +static void test_ns_type_to_bit_unknown(struct kunit *const test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + /* clang-format off */ + kunit_warning_suppress(test) { + /* clang-format on */ + KUNIT_EXPECT_EQ(test, 0ULL, + landlock_ns_type_to_bit(CLONE_THREAD)); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } +} + +static void test_ns_types_to_bits_all(struct kunit *const test) +{ + KUNIT_EXPECT_EQ(test, GENMASK_ULL(LANDLOCK_NUM_NAMESPACE_TYPE - 1, 0), + landlock_ns_types_to_bits(CLONE_NS_ALL)); +} + +static void test_ns_types_to_bits_single(struct kunit *const test) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(landlock_namespace_types); i++) + KUNIT_EXPECT_EQ( + test, + landlock_ns_type_to_bit(landlock_namespace_types[i]), + landlock_ns_types_to_bits(landlock_namespace_types[i])); +} + +static void test_ns_types_to_bits_unknown(struct kunit *const test) +{ + if (!IS_ENABLED(CONFIG_BUG)) + kunit_skip(test, "requires CONFIG_BUG"); + + /* clang-format off */ + kunit_warning_suppress(test) { + /* clang-format on */ + KUNIT_EXPECT_EQ(test, 0ULL, + landlock_ns_types_to_bits(CLONE_THREAD)); + KUNIT_EXPECT_SUPPRESSED_WARNING_COUNT(test, 1); + } +} + +static void test_ns_types_to_bits_zero(struct kunit *const test) +{ + KUNIT_EXPECT_EQ(test, 0ULL, landlock_ns_types_to_bits(0)); +} + +static struct kunit_case test_cases[] = { + KUNIT_CASE(test_ns_type_to_bit), + KUNIT_CASE(test_ns_type_to_bit_unknown), + KUNIT_CASE(test_ns_types_to_bits_all), + KUNIT_CASE(test_ns_types_to_bits_single), + KUNIT_CASE(test_ns_types_to_bits_unknown), + KUNIT_CASE(test_ns_types_to_bits_zero), + {} +}; + +static struct kunit_suite test_suite = { + .name = "landlock_ns", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/landlock/ns.h b/security/landlock/ns.h new file mode 100644 index 000000000000..46d3bd17479d --- /dev/null +++ b/security/landlock/ns.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Namespace hooks + * + * Copyright © 2026 Cloudflare, Inc. + */ + +#ifndef _SECURITY_LANDLOCK_NS_H +#define _SECURITY_LANDLOCK_NS_H + +#include <linux/types.h> + +u64 landlock_ns_type_to_bit(u32 ns_type); +u64 landlock_ns_types_to_bits(u64 ns_types); + +__init void landlock_add_ns_hooks(void); + +#endif /* _SECURITY_LANDLOCK_NS_H */ diff --git a/security/landlock/ruleset.c b/security/landlock/ruleset.c index 0d07707523cd..11776b138a56 100644 --- a/security/landlock/ruleset.c +++ b/security/landlock/ruleset.c @@ -31,15 +31,15 @@ #include <trace/events/landlock.h> -struct landlock_ruleset * -landlock_create_ruleset(const access_mask_t fs_access_mask, - const access_mask_t net_access_mask, - const access_mask_t scope_mask) +struct landlock_ruleset *landlock_create_ruleset( + const access_mask_t fs_access_mask, const access_mask_t net_access_mask, + const access_mask_t scope_mask, const access_mask_t permission_mask) { struct landlock_ruleset *new_ruleset; /* Informs about useless ruleset. */ - if (!fs_access_mask && !net_access_mask && !scope_mask) + if (!fs_access_mask && !net_access_mask && !scope_mask && + !permission_mask) return ERR_PTR(-ENOMSG); new_ruleset = kzalloc_obj(*new_ruleset, GFP_KERNEL_ACCOUNT); @@ -58,27 +58,38 @@ landlock_create_ruleset(const access_mask_t fs_access_mask, new_ruleset->id = landlock_get_id_range(1); #endif /* CONFIG_TRACEPOINTS */ - /* Should already be checked in landlock_create_ruleset(). */ + /* The caller must only pass supported access rights and scopes. */ if (fs_access_mask) { const access_mask_t mask = fs_access_mask & LANDLOCK_MASK_ACCESS_FS; WARN_ON_ONCE(fs_access_mask != mask); - new_ruleset->handled_masks.fs |= mask; + new_ruleset->layer.handled.fs |= mask; } + if (net_access_mask) { const access_mask_t mask = net_access_mask & LANDLOCK_MASK_ACCESS_NET; WARN_ON_ONCE(net_access_mask != mask); - new_ruleset->handled_masks.net |= mask; + new_ruleset->layer.handled.net |= mask; } + if (scope_mask) { const access_mask_t mask = scope_mask & LANDLOCK_MASK_SCOPE; WARN_ON_ONCE(scope_mask != mask); - new_ruleset->handled_masks.scope |= mask; + new_ruleset->layer.handled.scope |= mask; + } + + if (permission_mask) { + const access_mask_t mask = permission_mask & + LANDLOCK_MASK_PERMISSION; + + WARN_ON_ONCE(permission_mask != mask); + new_ruleset->layer.handled.permissions |= mask; } + return new_ruleset; } diff --git a/security/landlock/ruleset.h b/security/landlock/ruleset.h index b536fa0425b7..3a38d7079dc2 100644 --- a/security/landlock/ruleset.h +++ b/security/landlock/ruleset.h @@ -171,9 +171,10 @@ struct landlock_ruleset { * @version: Counter incremented on each successful * landlock_add_rule(2), including when it only extends an existing * rule's access rights. Used by tracepoints to correlate a domain with - * the exact ruleset state it was created from. Protected by @lock. + * the exact successful rule history it was created from. Protected by + * @lock. */ - u32 version; + u64 version; /** * @id: Unique identifier for this ruleset, used for tracing. */ @@ -181,22 +182,29 @@ struct landlock_ruleset { #endif /* CONFIG_TRACEPOINTS */ /** - * @quiet_masks: Stores the quiet flags for an unmerged ruleset. For a + * @quiet_access: Stores the quiet flags for an unmerged ruleset. For a * merged domain, this is stored in each layer's struct - * landlock_hierarchy instead. + * landlock_hierarchy instead. Its permissions member is unused because + * permission quieting is per member rather than per permission. */ - struct access_masks quiet_masks; + struct access_masks quiet_access; +#ifdef CONFIG_SECURITY_LANDLOCK_LOG /** - * @handled_masks: Contains the subset of filesystem and network actions - * that are handled by this ruleset. + * @quiet_permission: Per-member quiet bitmasks for permission types in + * this ruleset. A denied member whose bit is set here is not submitted + * to audit when this layer denies it. */ - struct access_masks handled_masks; + struct permission_masks quiet_permission; +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ + /** + * @layer: Access configuration for this ruleset's single mutable layer. + */ + struct layer_config layer; }; -struct landlock_ruleset * -landlock_create_ruleset(const access_mask_t access_mask_fs, - const access_mask_t access_mask_net, - const access_mask_t scope_mask); +struct landlock_ruleset *landlock_create_ruleset( + const access_mask_t access_mask_fs, const access_mask_t access_mask_net, + const access_mask_t scope_mask, const access_mask_t permission_mask); void landlock_put_ruleset(struct landlock_ruleset *const ruleset); @@ -247,4 +255,10 @@ static inline void landlock_get_ruleset(struct landlock_ruleset *const ruleset) refcount_inc(&ruleset->usage); } +static inline access_mask_t +landlock_get_permission_mask(const struct landlock_ruleset *const ruleset) +{ + return ruleset->layer.handled.permissions; +} + #endif /* _SECURITY_LANDLOCK_RULESET_H */ diff --git a/security/landlock/setup.c b/security/landlock/setup.c index 47dac1736f10..971419d663bb 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -11,12 +11,14 @@ #include <linux/lsm_hooks.h> #include <uapi/linux/lsm.h> +#include "cap.h" #include "common.h" #include "cred.h" #include "errata.h" #include "fs.h" #include "id.h" #include "net.h" +#include "ns.h" #include "setup.h" #include "task.h" @@ -68,6 +70,8 @@ static int __init landlock_init(void) landlock_add_task_hooks(); landlock_add_fs_hooks(); landlock_add_net_hooks(); + landlock_add_ns_hooks(); + landlock_add_cap_hooks(); landlock_init_id(); landlock_initialized = true; pr_info("Up and running.\n"); diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index 1d02d57f4c48..fb5af318b69b 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -20,6 +20,7 @@ #include <linux/fs.h> #include <linux/limits.h> #include <linux/mount.h> +#include <linux/ns/ns_common_types.h> #include <linux/path.h> #include <linux/sched.h> #include <linux/sched/signal.h> @@ -30,11 +31,13 @@ #include <linux/uaccess.h> #include <uapi/linux/landlock.h> +#include "cap.h" #include "cred.h" #include "domain.h" #include "fs.h" #include "limits.h" #include "net.h" +#include "ns.h" #include "ruleset.h" #include "setup.h" #include "tsync.h" @@ -98,7 +101,10 @@ static void build_check_abi(void) struct landlock_ruleset_attr ruleset_attr; struct landlock_path_beneath_attr path_beneath_attr; struct landlock_net_port_attr net_port_attr; + struct landlock_namespace_attr namespace_attr; + struct landlock_capability_attr capability_attr; size_t ruleset_size, path_beneath_size, net_port_size; + size_t namespace_size, capability_size; /* * For each user space ABI structures, first checks that there is no @@ -111,8 +117,9 @@ static void build_check_abi(void) ruleset_size += sizeof(ruleset_attr.quiet_access_fs); ruleset_size += sizeof(ruleset_attr.quiet_access_net); ruleset_size += sizeof(ruleset_attr.quiet_scoped); + ruleset_size += sizeof(ruleset_attr.handled_permissions); BUILD_BUG_ON(sizeof(ruleset_attr) != ruleset_size); - BUILD_BUG_ON(sizeof(ruleset_attr) != 48); + BUILD_BUG_ON(sizeof(ruleset_attr) != 56); path_beneath_size = sizeof(path_beneath_attr.allowed_access); path_beneath_size += sizeof(path_beneath_attr.parent_fd); @@ -123,6 +130,18 @@ static void build_check_abi(void) net_port_size += sizeof(net_port_attr.port); BUILD_BUG_ON(sizeof(net_port_attr) != net_port_size); BUILD_BUG_ON(sizeof(net_port_attr) != 16); + + namespace_size = sizeof(namespace_attr.permissions); + namespace_size += sizeof(namespace_attr.allowed_namespace_types); + namespace_size += sizeof(namespace_attr.quiet_namespace_types); + BUILD_BUG_ON(sizeof(namespace_attr) != namespace_size); + BUILD_BUG_ON(sizeof(namespace_attr) != 24); + + capability_size = sizeof(capability_attr.permissions); + capability_size += sizeof(capability_attr.allowed_capabilities); + capability_size += sizeof(capability_attr.quiet_capabilities); + BUILD_BUG_ON(sizeof(capability_attr) != capability_size); + BUILD_BUG_ON(sizeof(capability_attr) != 24); } /* Ruleset handling */ @@ -172,7 +191,7 @@ static const struct file_operations ruleset_fops = { * If the change involves a fix that requires userspace awareness, also update * the errata documentation in Documentation/userspace-api/landlock.rst . */ -const int landlock_abi_version = 11; +const int landlock_abi_version = 12; /** * sys_landlock_create_ruleset - Create a new ruleset @@ -197,14 +216,13 @@ const int landlock_abi_version = 11; * returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; - * - %EINVAL: unknown @flags, or unknown access, or unknown scope, or too small - * @size; + * - %EINVAL: unknown @flags, access, scope, or permission, or too small @size; * - %EINVAL: quiet_access_fs, quiet_access_net, or quiet_scoped is not a * subset of the corresponding handled_access_fs, handled_access_net, or * scoped; * - %E2BIG: @attr or @size inconsistencies; * - %EFAULT: @attr or @size inconsistencies; - * - %ENOMSG: empty &landlock_ruleset_attr.handled_access_fs. + * - %ENOMSG: all handled access, scope, and permission fields are empty. * * .. kernel-doc:: include/uapi/linux/landlock.h * :identifiers: landlock_create_ruleset_flags @@ -273,16 +291,22 @@ SYSCALL_DEFINE3(landlock_create_ruleset, ruleset_attr.scoped) return -EINVAL; + /* Checks permission content (and 32-bits cast). */ + if ((ruleset_attr.handled_permissions | LANDLOCK_MASK_PERMISSION) != + LANDLOCK_MASK_PERMISSION) + return -EINVAL; + /* Checks arguments and transforms to kernel struct. */ ruleset = landlock_create_ruleset(ruleset_attr.handled_access_fs, ruleset_attr.handled_access_net, - ruleset_attr.scoped); + ruleset_attr.scoped, + ruleset_attr.handled_permissions); if (IS_ERR(ruleset)) return PTR_ERR(ruleset); - ruleset->quiet_masks.fs = ruleset_attr.quiet_access_fs; - ruleset->quiet_masks.net = ruleset_attr.quiet_access_net; - ruleset->quiet_masks.scope = ruleset_attr.quiet_scoped; + ruleset->quiet_access.fs = ruleset_attr.quiet_access_fs; + ruleset->quiet_access.net = ruleset_attr.quiet_access_net; + ruleset->quiet_access.scope = ruleset_attr.quiet_scoped; /* * Emits before anon_inode_getfd() installs the file descriptor, while @@ -377,12 +401,12 @@ static int add_rule_path_beneath(struct landlock_ruleset *const ruleset, return -ENOMSG; /* Checks that allowed_access matches the @ruleset constraints. */ - mask = ruleset->handled_masks.fs; + mask = ruleset->layer.handled.fs; if ((path_beneath_attr.allowed_access | mask) != mask) return -EINVAL; /* Checks for useless quiet flag. */ - if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.fs) + if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_access.fs) return -EINVAL; /* Gets and checks the new rule. */ @@ -418,12 +442,12 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, return -ENOMSG; /* Checks that allowed_access matches the @ruleset constraints. */ - mask = ruleset->handled_masks.net; + mask = ruleset->layer.handled.net; if ((net_port_attr.allowed_access | mask) != mask) return -EINVAL; /* Checks for useless quiet flag. */ - if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_masks.net) + if (flags & LANDLOCK_ADD_RULE_QUIET && !ruleset->quiet_access.net) return -EINVAL; /* Denies inserting a rule with port greater than 65535. */ @@ -435,13 +459,164 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, net_port_attr.allowed_access, flags); } +static int add_rule_namespace(struct landlock_ruleset *const ruleset, + const void __user *const rule_attr, + const u32 flags) +{ + struct landlock_namespace_attr ns_attr; + access_mask_t mask; + u64 allowed_types, quiet_types; + int ret; + + /* + * Namespace rules support no add-rule flags. In particular, + * LANDLOCK_ADD_RULE_QUIET is filesystem/network only. + */ + if (flags) + return -EINVAL; + + /* Copies raw user space buffer. */ + ret = copy_from_user(&ns_attr, rule_attr, sizeof(ns_attr)); + if (ret) + return -EFAULT; + + /* Informs about useless rule: empty permissions. */ + if (!ns_attr.permissions) + return -ENOMSG; + + /* + * The permissions selector must match + * LANDLOCK_PERMISSION_NAMESPACE_USE. The valid set is a single bit + * today, so this is an exact match now; the check broadens to a subset + * test once another supported permission is added. + */ + if (ns_attr.permissions != LANDLOCK_PERMISSION_NAMESPACE_USE) + return -EINVAL; + + /* + * Checks that permissions match the ruleset constraints. This also + * makes quieting require the category to be handled. + */ + mask = landlock_get_permission_mask(ruleset); + if (!(mask & LANDLOCK_PERMISSION_NAMESPACE_USE)) + return -EINVAL; + + /* + * Informs about useless rule: neither allows nor quiets anything. A + * quiet-only rule (empty allowed set) is legal. + */ + if (!ns_attr.allowed_namespace_types && !ns_attr.quiet_namespace_types) + return -ENOMSG; + + /* + * Stores only the namespace types this kernel knows about. Unknown + * bits are silently accepted for forward compatibility: user space + * compiled against newer headers can pass new CLONE_NEW* flags without + * getting EINVAL on older kernels. Unknown bits have no effect because + * no hook checks them. The quiet bitmask suppresses logging of denials + * attributed to this layer; see landlock_log_denial(). + */ + allowed_types = ns_attr.allowed_namespace_types & CLONE_NS_ALL; + quiet_types = ns_attr.quiet_namespace_types & CLONE_NS_ALL; + + mutex_lock(&ruleset->lock); + ruleset->layer.allowed.ns_types |= + landlock_ns_types_to_bits(allowed_types); +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + ruleset->quiet_permission.ns_types |= + landlock_ns_types_to_bits(quiet_types); +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#ifdef CONFIG_TRACEPOINTS + ruleset->version++; +#endif /* CONFIG_TRACEPOINTS */ + trace_landlock_add_rule_namespace(ruleset, flags, ns_attr.permissions, + allowed_types, quiet_types); + mutex_unlock(&ruleset->lock); + return 0; +} + +static int add_rule_capability(struct landlock_ruleset *const ruleset, + const void __user *const rule_attr, + const u32 flags) +{ + struct landlock_capability_attr cap_attr; + access_mask_t mask; + u64 allowed_caps, quiet_caps; + int ret; + + /* + * Capability rules support no add-rule flags. In particular, + * LANDLOCK_ADD_RULE_QUIET is filesystem/network only. + */ + if (flags) + return -EINVAL; + + /* Copies raw user space buffer. */ + ret = copy_from_user(&cap_attr, rule_attr, sizeof(cap_attr)); + if (ret) + return -EFAULT; + + /* Informs about useless rule: empty permissions. */ + if (!cap_attr.permissions) + return -ENOMSG; + + /* + * The permissions selector must match + * LANDLOCK_PERMISSION_CAPABILITY_USE. The valid set is a single bit + * today, so this is an exact match now; the check broadens to a subset + * test once another supported permission is added. + */ + if (cap_attr.permissions != LANDLOCK_PERMISSION_CAPABILITY_USE) + return -EINVAL; + + /* + * Checks that permissions match the ruleset constraints. This also + * makes quieting require the category to be handled. + */ + mask = landlock_get_permission_mask(ruleset); + if (!(mask & LANDLOCK_PERMISSION_CAPABILITY_USE)) + return -EINVAL; + + /* + * Informs about useless rule: neither allows nor quiets anything. A + * quiet-only rule (empty allowed set) is legal. + */ + if (!cap_attr.allowed_capabilities && !cap_attr.quiet_capabilities) + return -ENOMSG; + + /* + * Stores only the capabilities this kernel knows about. Unknown bits + * are silently accepted for forward compatibility: user space compiled + * against newer headers can pass new CAP_* bits without getting EINVAL + * on older kernels. Unknown bits have no effect because no hook checks + * them. The quiet bitmask suppresses logging of denials attributed to + * this layer; see landlock_log_denial(). + */ + allowed_caps = cap_attr.allowed_capabilities & CAP_VALID_MASK; + quiet_caps = cap_attr.quiet_capabilities & CAP_VALID_MASK; + + mutex_lock(&ruleset->lock); + ruleset->layer.allowed.caps |= landlock_caps_to_bits(allowed_caps); +#ifdef CONFIG_SECURITY_LANDLOCK_LOG + ruleset->quiet_permission.caps |= landlock_caps_to_bits(quiet_caps); +#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#ifdef CONFIG_TRACEPOINTS + ruleset->version++; +#endif /* CONFIG_TRACEPOINTS */ + trace_landlock_add_rule_capability(ruleset, flags, cap_attr.permissions, + allowed_caps, quiet_caps); + mutex_unlock(&ruleset->lock); + return 0; +} + /** * sys_landlock_add_rule - Add a new rule to a ruleset * * @ruleset_fd: File descriptor tied to the ruleset that should be extended * with the new rule. * @rule_type: Identify the structure type pointed to by @rule_attr: - * %LANDLOCK_RULE_PATH_BENEATH or %LANDLOCK_RULE_NET_PORT. + * %LANDLOCK_RULE_PATH_BENEATH, %LANDLOCK_RULE_NET_PORT, + * %LANDLOCK_RULE_NAMESPACE, or %LANDLOCK_RULE_CAPABILITY. * @rule_attr: Pointer to a rule (matching the @rule_type). * @flags: Must be 0 or %LANDLOCK_ADD_RULE_QUIET. * @@ -458,11 +633,21 @@ static int add_rule_net_port(struct landlock_ruleset *ruleset, * &landlock_path_beneath_attr.allowed_access or * &landlock_net_port_attr.allowed_access is not a subset of the ruleset * handled accesses) + * - %EINVAL: A nonzero &landlock_namespace_attr.permissions is not + * %LANDLOCK_PERMISSION_NAMESPACE_USE or is not handled by the ruleset; + * - %EINVAL: A nonzero &landlock_capability_attr.permissions is not + * %LANDLOCK_PERMISSION_CAPABILITY_USE or is not handled by the ruleset; * - %EINVAL: &landlock_net_port_attr.port is greater than 65535; * - %EINVAL: LANDLOCK_ADD_RULE_QUIET is passed but the ruleset has no * quiet access bits set for the corresponding rule type. * - %ENOMSG: Empty accesses (e.g. &landlock_path_beneath_attr.allowed_access is * 0) and no flags; + * - %ENOMSG: &landlock_namespace_attr.permissions is 0, or both + * &landlock_namespace_attr.allowed_namespace_types and + * &landlock_namespace_attr.quiet_namespace_types are 0; + * - %ENOMSG: &landlock_capability_attr.permissions is 0, or both + * &landlock_capability_attr.allowed_capabilities and + * &landlock_capability_attr.quiet_capabilities are 0; * - %EBADF: @ruleset_fd is not a file descriptor for the current thread, or a * member of @rule_attr is not a file descriptor as expected; * - %EBADFD: @ruleset_fd is not a ruleset file descriptor, or a member of @@ -495,6 +680,10 @@ SYSCALL_DEFINE4(landlock_add_rule, const int, ruleset_fd, return add_rule_path_beneath(ruleset, rule_attr, flags); case LANDLOCK_RULE_NET_PORT: return add_rule_net_port(ruleset, rule_attr, flags); + case LANDLOCK_RULE_NAMESPACE: + return add_rule_namespace(ruleset, rule_attr, flags); + case LANDLOCK_RULE_CAPABILITY: + return add_rule_capability(ruleset, rule_attr, flags); default: return -EINVAL; } diff --git a/security/landlock/task.c b/security/landlock/task.c index 4491ce31ae04..d9eae86fc552 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -88,7 +88,9 @@ static int hook_ptrace_access_check(struct task_struct *const child, const unsigned int mode) { const struct landlock_cred_security *parent_subject; +#ifdef CONFIG_TRACEPOINTS u64 tracee_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ int err; /* Quick return for non-landlocked tasks. */ @@ -100,10 +102,10 @@ static int hook_ptrace_access_check(struct task_struct *const child, const struct landlock_domain *const child_dom = landlock_get_task_domain(child); err = domain_ptrace(parent_subject->domain, child_dom); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (child_dom) tracee_domain_id = child_dom->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!err) @@ -121,7 +123,12 @@ static int hook_ptrace_access_check(struct task_struct *const child, .u.tsk = child, }, .layer_plus_one = parent_subject->domain->num_layers, - .other_domain_id = tracee_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_ptrace = &(struct landlock_ptrace_trace) { + .tracee_domain_id = tracee_domain_id, + .tracer = current, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return err; @@ -142,7 +149,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent) { const struct landlock_cred_security *parent_subject; const struct landlock_domain *child_dom; - u64 tracee_domain_id = 0; int err; child_dom = landlock_get_current_domain(); @@ -154,12 +160,6 @@ static int hook_ptrace_traceme(struct task_struct *const parent) if (!err) return 0; -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - /* The tracee is the current task; its domain is stable here. */ - if (child_dom) - tracee_domain_id = child_dom->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - /* * For the ptrace_traceme case, we log the domain which is the cause of * the denial, which means the parent domain instead of the current @@ -174,7 +174,13 @@ static int hook_ptrace_traceme(struct task_struct *const parent) .u.tsk = current, }, .layer_plus_one = parent_subject->domain->num_layers, - .other_domain_id = tracee_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_ptrace = &(struct landlock_ptrace_trace) { + /* The current task's domain is stable here. */ + .tracee_domain_id = child_dom ? child_dom->hierarchy->id : 0, + .tracer = parent, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return err; } @@ -250,8 +256,7 @@ static bool domain_is_scoped(const struct landlock_domain *const client, } static bool sock_is_scoped(struct sock *const other, - const struct landlock_domain *const domain, - u64 *const peer_domain_id) + const struct landlock_domain *const domain) { const struct landlock_domain *dom_other; @@ -269,13 +274,23 @@ static bool sock_is_scoped(struct sock *const other, return false; dom_other = landlock_cred(other->sk_socket->file->f_cred)->domain; -#ifdef CONFIG_SECURITY_LANDLOCK_LOG - *peer_domain_id = dom_other ? dom_other->hierarchy->id : 0; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ return domain_is_scoped(domain, dom_other, LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET); } +#ifdef CONFIG_TRACEPOINTS + +static u64 get_socket_domain_id(const struct sock *const other) +{ + const struct landlock_domain *domain; + + lockdep_assert_held(&unix_sk(other)->lock); + domain = landlock_cred(other->sk_socket->file->f_cred)->domain; + return domain ? domain->hierarchy->id : 0; +} + +#endif /* CONFIG_TRACEPOINTS */ + static bool is_abstract_socket(struct sock *const sock) { struct unix_address *addr = unix_sk(sock)->addr; @@ -299,7 +314,6 @@ static int hook_unix_stream_connect(struct sock *const sock, struct sock *const newsk) { size_t handle_layer; - u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -311,7 +325,7 @@ static int hook_unix_stream_connect(struct sock *const sock, if (!is_abstract_socket(other)) return 0; - if (!sock_is_scoped(other, subject->domain, &peer_domain_id)) + if (!sock_is_scoped(other, subject->domain)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -323,7 +337,9 @@ static int hook_unix_stream_connect(struct sock *const sock, }, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = peer_domain_id, +#ifdef CONFIG_TRACEPOINTS + .other_domain_id = get_socket_domain_id(other), +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -332,7 +348,6 @@ static int hook_unix_may_send(struct socket *const sock, struct socket *const other) { size_t handle_layer; - u64 peer_domain_id = 0; const struct landlock_cred_security *const subject = landlock_get_applicable_subject(current_cred(), unix_scope, &handle_layer); @@ -350,7 +365,7 @@ static int hook_unix_may_send(struct socket *const sock, if (!is_abstract_socket(other->sk)) return 0; - if (!sock_is_scoped(other->sk, subject->domain, &peer_domain_id)) + if (!sock_is_scoped(other->sk, subject->domain)) return 0; landlock_log_denial(subject, &(struct landlock_request) { @@ -362,7 +377,9 @@ static int hook_unix_may_send(struct socket *const sock, }, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = peer_domain_id, +#ifdef CONFIG_TRACEPOINTS + .other_domain_id = get_socket_domain_id(other->sk), +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -377,7 +394,9 @@ static int hook_task_kill(struct task_struct *const p, { bool is_scoped; size_t handle_layer; +#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ const struct landlock_cred_security *subject; if (!cred) { @@ -409,10 +428,10 @@ static int hook_task_kill(struct task_struct *const p, is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!is_scoped) @@ -425,7 +444,12 @@ static int hook_task_kill(struct task_struct *const p, .u.tsk = p, }, .layer_plus_one = handle_layer + 1, - .other_domain_id = target_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_signal = &(struct landlock_signal_trace) { + .target_domain_id = target_domain_id, + .signal = sig, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } @@ -435,7 +459,9 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, { const struct landlock_cred_security *subject; bool is_scoped = false; +#ifdef CONFIG_TRACEPOINTS u64 target_domain_id = 0; +#endif /* CONFIG_TRACEPOINTS */ /* Lock already held by send_sigio() and send_sigurg(). */ lockdep_assert_held(&fown->lock); @@ -468,10 +494,10 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, is_scoped = domain_is_scoped(subject->domain, other, signal_scope.scope); -#ifdef CONFIG_SECURITY_LANDLOCK_LOG +#ifdef CONFIG_TRACEPOINTS if (other) target_domain_id = other->hierarchy->id; -#endif /* CONFIG_SECURITY_LANDLOCK_LOG */ +#endif /* CONFIG_TRACEPOINTS */ } if (!is_scoped) @@ -486,7 +512,12 @@ static int hook_file_send_sigiotask(struct task_struct *tsk, #ifdef CONFIG_SECURITY_LANDLOCK_LOG .layer_plus_one = landlock_file(fown->file)->fown_layer + 1, #endif /* CONFIG_SECURITY_LANDLOCK_LOG */ - .other_domain_id = target_domain_id, +#ifdef CONFIG_TRACEPOINTS + .trace_signal = &(struct landlock_signal_trace) { + .target_domain_id = target_domain_id, + .signal = signum ? signum : SIGIO, + }, +#endif /* CONFIG_TRACEPOINTS */ }); return -EPERM; } diff --git a/security/landlock/trace.c b/security/landlock/trace.c index 2ea7aac8d75d..995c6b948e16 100644 --- a/security/landlock/trace.c +++ b/security/landlock/trace.c @@ -6,11 +6,13 @@ * Copyright © 2026 Cloudflare, Inc. */ +#include <kunit/test.h> #include <linux/cleanup.h> #include <linux/dcache.h> #include <linux/err.h> #include <linux/fs.h> #include <linux/lsm_audit.h> +#include <linux/socket.h> #include <net/sock.h> #include "access.h" @@ -60,8 +62,8 @@ void landlock_trace_free_domain(const struct landlock_hierarchy *const hierarchy * * @request: Detail of the user space request. * @youngest_denied: The youngest hierarchy node that denied the access. - * @missing: The set of denied access rights. - * @same_exec: Whether the current task is the same executable that called + * @missing: The final missing access or permission subset, when applicable. + * @same_exec: Whether the policy subject is the same executable that called * landlock_restrict_self() for the denying domain, as computed * by landlock_log_denial(). * @logged: Whether the domain's policy selects this denial for logging, as @@ -79,9 +81,38 @@ void landlock_trace_denial( const access_mask_t missing, const bool same_exec, const bool logged) { switch (request->type) { + case LANDLOCK_REQUEST_NAMESPACE: + if (trace_landlock_deny_permission_namespace_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; + + trace_landlock_deny_permission_namespace( + youngest_denied, same_exec, logged, &blockers, + request->audit.u.ns.ns_type, + request->audit.u.ns.ns_id); + } + break; + case LANDLOCK_REQUEST_CAPABILITY: + if (trace_landlock_deny_permission_capability_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; + + trace_landlock_deny_permission_capability( + youngest_denied, same_exec, logged, &blockers, + request->audit.u.cap); + } + break; case LANDLOCK_REQUEST_FS_ACCESS: case LANDLOCK_REQUEST_FS_CHANGE_TOPOLOGY: if (trace_landlock_deny_access_fs_enabled()) { + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; char *buf __free(__putname) = __getname(); struct path dentry_path; const char *pathname; @@ -146,29 +177,67 @@ void landlock_trace_denial( trace_landlock_deny_access_fs(youngest_denied, same_exec, logged, - missing, path, pathname); + &blockers, path, + pathname); } break; case LANDLOCK_REQUEST_NET_ACCESS: - if (trace_landlock_deny_access_net_enabled()) + if (trace_landlock_deny_access_net_enabled()) { + const struct landlock_net_trace *const trace_net = + request->trace_net; + const struct landlock_blockers blockers = { + .access = missing, + .type = request->type, + }; + struct sockaddr_storage address = {}; + + if (WARN_ON_ONCE(!trace_net || !trace_net->address)) + return; + + if (WARN_ON_ONCE( + trace_net->addrlen < + (int)offsetofend(struct sockaddr, + sa_family) || + trace_net->addrlen > (int)sizeof(address))) + return; + + memcpy(&address, trace_net->address, + trace_net->addrlen); trace_landlock_deny_access_net( - youngest_denied, same_exec, logged, missing, + youngest_denied, same_exec, logged, &blockers, request->audit.u.net->sk, - ntohs(request->audit.u.net->sport), - ntohs(request->audit.u.net->dport)); + trace_net->socket_family, &address, + trace_net->addrlen); + } break; case LANDLOCK_REQUEST_PTRACE: - if (trace_landlock_deny_ptrace_enabled()) - trace_landlock_deny_ptrace(youngest_denied, same_exec, - logged, - request->other_domain_id, - request->audit.u.tsk); + if (trace_landlock_deny_ptrace_enabled()) { + const struct landlock_ptrace_trace *const trace_ptrace = + request->trace_ptrace; + + if (WARN_ON_ONCE(!trace_ptrace || + !trace_ptrace->tracer)) + return; + + trace_landlock_deny_ptrace( + youngest_denied, same_exec, logged, + trace_ptrace->tracee_domain_id, + request->audit.u.tsk, trace_ptrace->tracer); + } break; case LANDLOCK_REQUEST_SCOPE_SIGNAL: - if (trace_landlock_deny_scope_signal_enabled()) + if (trace_landlock_deny_scope_signal_enabled()) { + const struct landlock_signal_trace *const trace_signal = + request->trace_signal; + + if (WARN_ON_ONCE(!trace_signal)) + return; + trace_landlock_deny_scope_signal( youngest_denied, same_exec, logged, - request->other_domain_id, request->audit.u.tsk); + trace_signal->target_domain_id, + request->audit.u.tsk, trace_signal->signal); + } break; case LANDLOCK_REQUEST_SCOPE_ABSTRACT_UNIX_SOCKET: if (trace_landlock_deny_scope_abstract_unix_socket_enabled()) @@ -183,3 +252,184 @@ void landlock_trace_denial( break; } } + +#ifdef CONFIG_SECURITY_LANDLOCK_KUNIT_TEST + +static void test_trace_seq_init(struct trace_seq *const seq, const size_t size) +{ + memset(seq, 0, sizeof(*seq)); + seq_buf_init(&seq->seq, seq->buffer, size); +} + +static void test_untrusted_str_data(struct kunit *const test) +{ + const char binary[] = { 'a', '\0', '<' }; + static const char ellipsis[] = "\xe2\x80\xa6"; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, "<too_long>", 10); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "<too_long>"); + + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, binary, sizeof(binary)); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "a\\000<"); + + /* Input ellipsis bytes are escaped and cannot mimic the raw marker. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, ellipsis, + sizeof(ellipsis) - 1); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "\\342\\200\\246"); +} + +static void test_untrusted_str_boundaries(struct kunit *const test) +{ + static const char escaped_space[] = "\\040"; + const size_t output_size = TRACE_UNTRUSTED_STR_OUTPUT_SIZE; + const size_t marker_len = sizeof(TRACE_TRUNCATION_MARKER) - 1; + const size_t escape_len = sizeof(escaped_space) - 1; + const size_t exact_prefix_len = + output_size - marker_len - 1 - escape_len; + const size_t short_prefix_len = exact_prefix_len + 1; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const input = kunit_kmalloc(test, output_size + 1, GFP_KERNEL); + char *const expected = kunit_kmalloc(test, output_size, GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, input); + KUNIT_ASSERT_NOT_NULL(test, expected); + + /* The escaped string and its trailing NUL exactly fit the limit. */ + memset(input, 'a', output_size - 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, output_size - 1); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_EQ(test, seq->seq.len, output_size); + KUNIT_EXPECT_EQ(test, memcmp(output, input, output_size - 1), 0); + + /* Stop before a four-byte escape when only three bytes remain. */ + memset(input, 'a', short_prefix_len); + input[short_prefix_len] = ' '; + memset(input + short_prefix_len + 1, 'b', 5); + memset(expected, 'a', short_prefix_len); + memcpy(expected + short_prefix_len, TRACE_TRUNCATION_MARKER, + marker_len + 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, short_prefix_len + 6); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, expected); + + /* Include a four-byte escape that exactly fills the prefix capacity. */ + memset(input, 'a', exact_prefix_len); + input[exact_prefix_len] = ' '; + memset(input + exact_prefix_len + 1, 'b', marker_len + 1); + memset(expected, 'a', exact_prefix_len); + memcpy(expected + exact_prefix_len, escaped_space, escape_len); + memcpy(expected + exact_prefix_len + escape_len, + TRACE_TRUNCATION_MARKER, marker_len + 1); + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, input, + exact_prefix_len + marker_len + 2); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, expected); + + /* Literal backslashes remain escaped in complete output. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + output = __trace_print_untrusted_str(seq, "/\\000", 5); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "/\\\\000"); +} + +static void test_untrusted_str_cursor(struct kunit *const test) +{ + const size_t padding_len = + TRACE_SEQ_BUFFER_SIZE - TRACE_UNTRUSTED_STR_OUTPUT_SIZE + 1; + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const padding = kunit_kzalloc(test, padding_len, GFP_KERNEL); + const char *output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, padding); + + /* Accept available space exactly equal to the fixed reservation. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + trace_seq_putmem(seq, padding, padding_len - 1); + output = __trace_print_untrusted_str(seq, "/a", 2); + KUNIT_ASSERT_NOT_NULL(test, output); + KUNIT_EXPECT_STREQ(test, output, "/a"); + KUNIT_EXPECT_EQ(test, seq->seq.len, padding_len - 1 + sizeof("/a")); + + /* Reject one byte less without changing the scratch cursor. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + trace_seq_putmem(seq, padding, padding_len); + output = __trace_print_untrusted_str(seq, "/a", 2); + KUNIT_EXPECT_NULL(test, output); + KUNIT_EXPECT_EQ(test, seq->seq.len, padding_len); +} + +static void test_untrusted_str_composition(struct kunit *const test) +{ + static const struct trace_print_flags flags[] = { + { .mask = 1, .name = "read" }, + }; + const size_t output_size = TRACE_UNTRUSTED_STR_OUTPUT_SIZE; + const size_t prefix_len = output_size - sizeof(TRACE_TRUNCATION_MARKER); + struct trace_seq *const seq = + kunit_kzalloc(test, sizeof(*seq), GFP_KERNEL); + char *const expected = kunit_kmalloc(test, output_size, GFP_KERNEL); + char *const path = kunit_kmalloc(test, output_size, GFP_KERNEL); + const char *flags_output, *path_output; + + KUNIT_ASSERT_NOT_NULL(test, seq); + KUNIT_ASSERT_NOT_NULL(test, expected); + KUNIT_ASSERT_NOT_NULL(test, path); + memset(path, 'a', output_size); + memset(expected, 'a', prefix_len); + memcpy(expected + prefix_len, TRACE_TRUNCATION_MARKER, + sizeof(TRACE_TRUNCATION_MARKER)); + + /* Exercise both legal TP_printk() sibling evaluation orders. */ + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + path_output = __trace_print_untrusted_str(seq, path, output_size); + flags_output = + trace_print_flags_seq(seq, "|", 1, flags, ARRAY_SIZE(flags)); + KUNIT_ASSERT_NOT_NULL(test, path_output); + KUNIT_EXPECT_STREQ(test, path_output, expected); + KUNIT_EXPECT_STREQ(test, flags_output, "read"); + + test_trace_seq_init(seq, TRACE_SEQ_BUFFER_SIZE); + flags_output = + trace_print_flags_seq(seq, "|", 1, flags, ARRAY_SIZE(flags)); + path_output = __trace_print_untrusted_str(seq, path, output_size); + KUNIT_ASSERT_NOT_NULL(test, path_output); + KUNIT_EXPECT_STREQ(test, path_output, expected); + KUNIT_EXPECT_STREQ(test, flags_output, "read"); +} + +static struct kunit_case test_cases[] = { + /* clang-format off */ + KUNIT_CASE(test_untrusted_str_data), + KUNIT_CASE(test_untrusted_str_boundaries), + KUNIT_CASE(test_untrusted_str_cursor), + KUNIT_CASE(test_untrusted_str_composition), + {} + /* clang-format on */ +}; + +static struct kunit_suite test_suite = { + .name = "landlock_trace", + .test_cases = test_cases, +}; + +kunit_test_suite(test_suite); + +#endif /* CONFIG_SECURITY_LANDLOCK_KUNIT_TEST */ diff --git a/security/lsm_audit.c b/security/lsm_audit.c index 737f5a263a8f..955b2e7b2c8e 100644 --- a/security/lsm_audit.c +++ b/security/lsm_audit.c @@ -182,7 +182,7 @@ void audit_log_lsm_data(struct audit_buffer *ab, * start making this union too large! See struct lsm_network_audit * as an example of how to deal with large data. */ - BUILD_BUG_ON(sizeof(a->u) > sizeof(void *)*2); + BUILD_BUG_ON(sizeof(a->u) > (sizeof(u64) * 2)); switch (a->type) { case LSM_AUDIT_DATA_NONE: @@ -231,7 +231,7 @@ void audit_log_lsm_data(struct audit_buffer *ab, audit_log_format(ab, " ino=%llu", inode->i_ino); } - audit_log_format(ab, " ioctlcmd=0x%hx", a->u.op->cmd); + audit_log_format(ab, " ioctlcmd=0x%x", a->u.op->cmd); break; } case LSM_AUDIT_DATA_DENTRY: { @@ -403,6 +403,10 @@ void audit_log_lsm_data(struct audit_buffer *ab, case LSM_AUDIT_DATA_NLMSGTYPE: audit_log_format(ab, " nl-msgtype=%hu", a->u.nlmsg_type); break; + case LSM_AUDIT_DATA_NS: + audit_log_format(ab, " namespace_type=0x%x namespace_id=%llu", + a->u.ns.ns_type, a->u.ns.ns_id); + break; } /* switch (a->type) */ } diff --git a/security/lsm_init.c b/security/lsm_init.c index a1ad641811de..8d5ecea99c46 100644 --- a/security/lsm_init.c +++ b/security/lsm_init.c @@ -290,7 +290,6 @@ static void __init lsm_prepare(struct lsm_info *lsm) return; /* Register the LSM blob sizes. */ - blobs = lsm->blobs; lsm_blob_size_update(&blobs->lbs_cred, &blob_sizes.lbs_cred); lsm_blob_size_update(&blobs->lbs_file, &blob_sizes.lbs_file); lsm_blob_size_update(&blobs->lbs_backing_file, @@ -303,6 +302,7 @@ static void __init lsm_prepare(struct lsm_info *lsm) lsm_blob_size_update(&blobs->lbs_ipc, &blob_sizes.lbs_ipc); lsm_blob_size_update(&blobs->lbs_key, &blob_sizes.lbs_key); lsm_blob_size_update(&blobs->lbs_msg_msg, &blob_sizes.lbs_msg_msg); + lsm_blob_size_update(&blobs->lbs_ns, &blob_sizes.lbs_ns); lsm_blob_size_update(&blobs->lbs_perf_event, &blob_sizes.lbs_perf_event); lsm_blob_size_update(&blobs->lbs_sock, &blob_sizes.lbs_sock); @@ -431,8 +431,7 @@ int __init security_init(void) } if (lsm_order_cmdline) { - if (lsm_order_legacy) - lsm_order_legacy = NULL; + lsm_order_legacy = NULL; lsm_order_parse(lsm_order_cmdline, "cmdline"); } else lsm_order_parse(lsm_order_builtin, "builtin"); @@ -450,6 +449,7 @@ int __init security_init(void) lsm_pr("blob(ipc) size %d\n", blob_sizes.lbs_ipc); lsm_pr("blob(key) size %d\n", blob_sizes.lbs_key); lsm_pr("blob(msg_msg)_size %d\n", blob_sizes.lbs_msg_msg); + lsm_pr("blob(ns) size %d\n", blob_sizes.lbs_ns); lsm_pr("blob(sock) size %d\n", blob_sizes.lbs_sock); lsm_pr("blob(superblock) size %d\n", blob_sizes.lbs_superblock); lsm_pr("blob(perf_event) size %d\n", blob_sizes.lbs_perf_event); @@ -476,8 +476,7 @@ int __init security_init(void) blob_sizes.lbs_inode, 0, SLAB_PANIC, NULL); - if (lsm_cred_alloc((struct cred *)unrcu_pointer(current->cred), - GFP_KERNEL)) + if (lsm_cred_alloc((struct cred *)current->cred, GFP_KERNEL)) panic("early LSM cred alloc failed\n"); if (lsm_task_alloc(current)) panic("early LSM task alloc failed\n"); diff --git a/security/security.c b/security/security.c index 2ee276ab15c5..f6583d507395 100644 --- a/security/security.c +++ b/security/security.c @@ -26,6 +26,7 @@ #include <linux/string.h> #include <linux/xattr.h> #include <linux/msg.h> +#include <linux/ns_common.h> #include <linux/overflow.h> #include <linux/perf_event.h> #include <linux/fs.h> @@ -382,6 +383,19 @@ static int lsm_superblock_alloc(struct super_block *sb) } /** + * lsm_ns_alloc - allocate a composite namespace blob + * @ns: the namespace that needs a blob + * + * Allocate the namespace blob for all the modules + * + * Returns 0, or -ENOMEM if memory can't be allocated. + */ +static int lsm_ns_alloc(struct ns_common *ns) +{ + return lsm_blob_alloc(&ns->ns_security, blob_sizes.lbs_ns, GFP_KERNEL); +} + +/** * lsm_fill_user_ctx - Fill a user space lsm_ctx structure * @uctx: a userspace LSM context to be filled * @uctx_len: available uctx size (input), used uctx size (output) @@ -1425,7 +1439,7 @@ EXPORT_SYMBOL(security_path_mknod); * * Update inode security field after a regular file has been created. */ -void security_path_post_mknod(struct mnt_idmap *idmap, struct dentry *dentry) +void security_path_post_mknod(const struct mnt_idmap *idmap, struct dentry *dentry) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) return; @@ -1614,6 +1628,7 @@ int security_path_chroot(const struct path *path) /** * security_inode_create() - Check if creating a file is allowed + * @idmap: idmap of the mount * @dir: the parent directory * @dentry: the file being created * @mode: requested file mode @@ -1622,12 +1637,12 @@ int security_path_chroot(const struct path *path) * * Return: Returns 0 if permission is granted. */ -int security_inode_create(struct inode *dir, struct dentry *dentry, - umode_t mode) +int security_inode_create(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { if (unlikely(IS_PRIVATE(dir))) return 0; - return call_int_hook(inode_create, dir, dentry, mode); + return call_int_hook(inode_create, idmap, dir, dentry, mode); } EXPORT_SYMBOL_GPL(security_inode_create); @@ -1638,7 +1653,7 @@ EXPORT_SYMBOL_GPL(security_inode_create); * * Update inode security data after a tmpfile has been created. */ -void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, +void security_inode_post_create_tmpfile(const struct mnt_idmap *idmap, struct inode *inode) { if (unlikely(IS_PRIVATE(inode))) @@ -1648,6 +1663,7 @@ void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, /** * security_inode_link() - Check if creating a hard link is allowed + * @idmap: idmap of the mount * @old_dentry: existing file * @dir: new parent directory * @new_dentry: new link @@ -1656,12 +1672,12 @@ void security_inode_post_create_tmpfile(struct mnt_idmap *idmap, * * Return: Returns 0 if permission is granted. */ -int security_inode_link(struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry) +int security_inode_link(const struct mnt_idmap *idmap, struct dentry *old_dentry, + struct inode *dir, struct dentry *new_dentry) { if (unlikely(IS_PRIVATE(d_backing_inode(old_dentry)))) return 0; - return call_int_hook(inode_link, old_dentry, dir, new_dentry); + return call_int_hook(inode_link, idmap, old_dentry, dir, new_dentry); } /** @@ -1682,6 +1698,7 @@ int security_inode_unlink(struct inode *dir, struct dentry *dentry) /** * security_inode_symlink() - Check if creating a symbolic link is allowed + * @idmap: idmap of the mount * @dir: parent directory * @dentry: symbolic link * @old_name: existing filename @@ -1690,16 +1707,17 @@ int security_inode_unlink(struct inode *dir, struct dentry *dentry) * * Return: Returns 0 if permission is granted. */ -int security_inode_symlink(struct inode *dir, struct dentry *dentry, - const char *old_name) +int security_inode_symlink(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name) { if (unlikely(IS_PRIVATE(dir))) return 0; - return call_int_hook(inode_symlink, dir, dentry, old_name); + return call_int_hook(inode_symlink, idmap, dir, dentry, old_name); } /** * security_inode_mkdir() - Check if creating a new directory is allowed + * @idmap: idmap of the mount * @dir: parent directory * @dentry: new directory * @mode: new directory mode @@ -1709,11 +1727,12 @@ int security_inode_symlink(struct inode *dir, struct dentry *dentry, * * Return: Returns 0 if permission is granted. */ -int security_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +int security_inode_mkdir(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { if (unlikely(IS_PRIVATE(dir))) return 0; - return call_int_hook(inode_mkdir, dir, dentry, mode); + return call_int_hook(inode_mkdir, idmap, dir, dentry, mode); } EXPORT_SYMBOL_GPL(security_inode_mkdir); @@ -1735,6 +1754,7 @@ int security_inode_rmdir(struct inode *dir, struct dentry *dentry) /** * security_inode_mknod() - Check if creating a special file is allowed + * @idmap: idmap of the mount * @dir: parent directory * @dentry: new file * @mode: new file mode @@ -1747,12 +1767,12 @@ int security_inode_rmdir(struct inode *dir, struct dentry *dentry) * * Return: Returns 0 if permission is granted. */ -int security_inode_mknod(struct inode *dir, struct dentry *dentry, - umode_t mode, dev_t dev) +int security_inode_mknod(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) { if (unlikely(IS_PRIVATE(dir))) return 0; - return call_int_hook(inode_mknod, dir, dentry, mode, dev); + return call_int_hook(inode_mknod, idmap, dir, dentry, mode, dev); } /** @@ -1823,6 +1843,7 @@ int security_inode_follow_link(struct dentry *dentry, struct inode *inode, /** * security_inode_permission() - Check if accessing an inode is allowed + * @idmap: idmap of the mount * @inode: inode * @mask: access mask * @@ -1835,11 +1856,12 @@ int security_inode_follow_link(struct dentry *dentry, struct inode *inode, * * Return: Returns 0 if permission is granted. */ -int security_inode_permission(struct inode *inode, int mask) +int security_inode_permission(const struct mnt_idmap *idmap, + struct inode *inode, int mask) { if (unlikely(IS_PRIVATE(inode))) return 0; - return call_int_hook(inode_permission, inode, mask); + return call_int_hook(inode_permission, idmap, inode, mask); } /** @@ -1855,7 +1877,7 @@ int security_inode_permission(struct inode *inode, int mask) * * Return: Returns 0 if permission is granted. */ -int security_inode_setattr(struct mnt_idmap *idmap, +int security_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) @@ -1872,7 +1894,7 @@ EXPORT_SYMBOL_GPL(security_inode_setattr); * * Update inode security field after successful setting file attributes. */ -void security_inode_post_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, int ia_valid) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) @@ -1921,7 +1943,7 @@ int security_inode_getattr(const struct path *path) * * Return: Returns 0 if permission is granted. */ -int security_inode_setxattr(struct mnt_idmap *idmap, +int security_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -1953,7 +1975,7 @@ int security_inode_setxattr(struct mnt_idmap *idmap, * * Return: Returns 0 if permission is granted. */ -int security_inode_set_acl(struct mnt_idmap *idmap, +int security_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { @@ -1990,7 +2012,7 @@ void security_inode_post_set_acl(struct dentry *dentry, const char *acl_name, * * Return: Returns 0 if permission is granted. */ -int security_inode_get_acl(struct mnt_idmap *idmap, +int security_inode_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) @@ -2009,7 +2031,7 @@ int security_inode_get_acl(struct mnt_idmap *idmap, * * Return: Returns 0 if permission is granted. */ -int security_inode_remove_acl(struct mnt_idmap *idmap, +int security_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) @@ -2026,7 +2048,7 @@ int security_inode_remove_acl(struct mnt_idmap *idmap, * Update inode security data after successfully removing posix acls on * @dentry in @idmap. The posix acls are identified by @acl_name. */ -void security_inode_post_remove_acl(struct mnt_idmap *idmap, +void security_inode_post_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { if (unlikely(IS_PRIVATE(d_backing_inode(dentry)))) @@ -2108,7 +2130,7 @@ int security_inode_listxattr(struct dentry *dentry) * * Return: Returns 0 if permission is granted. */ -int security_inode_removexattr(struct mnt_idmap *idmap, +int security_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { int rc; @@ -2197,7 +2219,7 @@ int security_inode_need_killpriv(struct dentry *dentry) * Return: Return 0 on success. If error is returned, then the operation * causing setuid bit removal is failed. */ -int security_inode_killpriv(struct mnt_idmap *idmap, +int security_inode_killpriv(const struct mnt_idmap *idmap, struct dentry *dentry) { return call_int_hook(inode_killpriv, idmap, dentry); @@ -2219,7 +2241,7 @@ int security_inode_killpriv(struct mnt_idmap *idmap, * * Return: Returns size of buffer on success. */ -int security_inode_getsecurity(struct mnt_idmap *idmap, +int security_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) { @@ -2486,9 +2508,8 @@ void security_backing_file_free(struct file *backing_file) { void *blob = backing_file_security(backing_file); - call_void_hook(backing_file_free, backing_file); - if (blob) { + call_void_hook(backing_file_free, backing_file); backing_file_set_security(backing_file, NULL); kmem_cache_free(lsm_backing_file_cache, blob); } @@ -3358,6 +3379,64 @@ int security_create_user_ns(const struct cred *cred) } /** + * security_namespace_init() - Initialize LSM security data for a namespace + * @ns: the namespace being initialized + * + * Initialize the LSM security blob attached to the namespace. The namespace type + * is available via ns->ns_type, and the owning user namespace (if any) + * via ns->ops->owner(ns). + * + * Return: Returns 0 if successful, otherwise < 0 error code. + */ +int security_namespace_init(struct ns_common *ns) +{ + int rc; + + rc = lsm_ns_alloc(ns); + if (unlikely(rc)) + return rc; + + rc = call_int_hook(namespace_init, ns); + if (unlikely(rc)) + security_namespace_free(ns); + + return rc; +} + +/** + * security_namespace_free() - Release LSM security data from a namespace + * @ns: the namespace being freed + * + * Release security data attached to the namespace. Called before the + * namespace structure is freed. + */ +void security_namespace_free(struct ns_common *ns) +{ + if (!ns->ns_security) + return; + + call_void_hook(namespace_free, ns); + + kfree(ns->ns_security); + ns->ns_security = NULL; +} + +/** + * security_namespace_install() - Check permission to install a namespace + * @nsset: the target nsset being configured + * @ns: the namespace being installed + * + * Check permission before allowing a namespace to be installed into the + * process's set of namespaces via setns(2). + * + * Return: Returns 0 if permission is granted, otherwise < 0 error code. + */ +int security_namespace_install(const struct nsset *nsset, struct ns_common *ns) +{ + return call_int_hook(namespace_install, nsset, ns); +} + +/** * security_ipc_permission() - Check if sysv ipc access is allowed * @ipcp: ipc permission structure * @flag: requested permissions diff --git a/security/selinux/avc.c b/security/selinux/avc.c index a9401d6c2e5f..2a9d4888384b 100644 --- a/security/selinux/avc.c +++ b/security/selinux/avc.c @@ -190,8 +190,8 @@ avc_xperms_decision_lookup(u8 driver, u8 base_perm, } static inline unsigned int -avc_xperms_has_perm(struct extended_perms_decision *xpd, - u8 perm, u8 which) +avc_xperms_has_perm(const struct extended_perms_decision *xpd, + u8 perm, u8 which) { unsigned int rc = 0; @@ -247,7 +247,7 @@ static void avc_xperms_free(struct avc_xperms_node *xp_node) } static void avc_copy_xperms_decision(struct extended_perms_decision *dest, - struct extended_perms_decision *src) + const struct extended_perms_decision *src) { dest->base_perm = src->base_perm; dest->driver = src->driver; @@ -269,7 +269,7 @@ static void avc_copy_xperms_decision(struct extended_perms_decision *dest, */ static inline void avc_quick_copy_xperms_decision(u8 perm, struct extended_perms_decision *dest, - struct extended_perms_decision *src) + const struct extended_perms_decision *src) { /* * compute index of the u32 of the 256 bits (8 u32s) that contain this @@ -323,7 +323,7 @@ error: } static int avc_add_xperms_decision(struct avc_node *node, - struct extended_perms_decision *src) + const struct extended_perms_decision *src) { struct avc_xperms_decision_node *dest_xpd; @@ -348,7 +348,7 @@ static struct avc_xperms_node *avc_xperms_alloc(void) } static int avc_xperms_populate(struct avc_node *node, - struct avc_xperms_node *src) + const struct avc_xperms_node *src) { struct avc_xperms_node *dest; struct avc_xperms_decision_node *dest_xpd; @@ -381,14 +381,17 @@ error: } static inline u32 avc_xperms_audit_required(u32 requested, - struct av_decision *avd, - struct extended_perms_decision *xpd, - u8 perm, - int result, - u32 *deniedp) + const struct av_decision *avd, + const struct extended_perms_decision *xpd, + u8 perm, + int result, + u32 *deniedp) { u32 denied, audited; + if (avd->flags & AVD_FLAGS_NEVERAUDIT) + return 0; + denied = requested & ~avd->allowed; if (unlikely(denied)) { audited = denied & avd->auditdeny; @@ -411,8 +414,8 @@ static inline u32 avc_xperms_audit_required(u32 requested, } static inline int avc_xperms_audit(u32 ssid, u32 tsid, u16 tclass, - u32 requested, struct av_decision *avd, - struct extended_perms_decision *xpd, + u32 requested, const struct av_decision *avd, + const struct extended_perms_decision *xpd, u8 perm, int result, struct common_audit_data *ad) { @@ -509,7 +512,8 @@ static struct avc_node *avc_alloc_node(void) return node; } -static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, u16 tclass, struct av_decision *avd) +static void avc_node_populate(struct avc_node *node, u32 ssid, u32 tsid, + u16 tclass, const struct av_decision *avd) { node->ae.ssid = ssid; node->ae.tsid = tsid; @@ -578,7 +582,7 @@ static int avc_latest_notif_update(u32 seqno, int is_insert) } } else { if (seqno > selinux_avc.avc_cache.latest_notif) - selinux_avc.avc_cache.latest_notif = seqno; + WRITE_ONCE(selinux_avc.avc_cache.latest_notif, seqno); } spin_unlock_irqrestore(¬if_lock, flag); @@ -603,7 +607,8 @@ static int avc_latest_notif_update(u32 seqno, int is_insert) * the access vectors into a cache entry. */ static void avc_insert(u32 ssid, u32 tsid, u16 tclass, - struct av_decision *avd, struct avc_xperms_node *xp_node) + const struct av_decision *avd, + const struct avc_xperms_node *xp_node) { struct avc_node *pos, *node = NULL; u32 hvalue; @@ -828,7 +833,7 @@ out: */ static int avc_update_node(u32 event, u32 perms, u8 driver, u8 base_perm, u8 xperm, u32 ssid, u32 tsid, u16 tclass, u32 seqno, - struct extended_perms_decision *xpd, u32 flags) + const struct extended_perms_decision *xpd, u32 flags) { u32 hvalue; int rc = 0; @@ -1149,8 +1154,11 @@ inline int avc_has_perm_noaudit(u32 ssid, u32 tsid, u32 denied; struct avc_node *node; - if (WARN_ON(!requested)) + if (WARN_ON(!requested)) { + /* Provide a deny-all, audit-all decision to the caller. */ + *avd = (struct av_decision){ .auditdeny = 0xffffffff }; return -EACCES; + } rcu_read_lock(); node = avc_lookup(ssid, tsid, tclass); @@ -1203,5 +1211,6 @@ int avc_has_perm(u32 ssid, u32 tsid, u16 tclass, u32 avc_policy_seqno(void) { - return selinux_avc.avc_cache.latest_notif; + /* Writers hold notif_lock; readers only sample the sequence number. */ + return READ_ONCE(selinux_avc.avc_cache.latest_notif); } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 035aaf113d1d..6168dd5f91c2 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -690,8 +690,12 @@ static int selinux_set_mnt_opts(struct super_block *sb, * will be used for both mounts) */ if ((sbsec->flags & SE_SBINITIALIZED) && (sb->s_type->fs_flags & FS_BINARY_MOUNTDATA) - && !opts) + && !opts) { + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && + sbsec->behavior == SECURITY_FS_USE_NATIVE) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; goto out; + } root_isec = backing_inode_security_novalidate(root); @@ -735,6 +739,9 @@ static int selinux_set_mnt_opts(struct super_block *sb, /* previously mounted with options, but not on this attempt? */ if ((sbsec->flags & SE_MNTMASK) && !opts) goto out_double_mount; + if ((kern_flags & SECURITY_LSM_NATIVE_LABELS) && + sbsec->behavior == SECURITY_FS_USE_NATIVE) + *set_kern_flags |= SECURITY_LSM_NATIVE_LABELS; rc = 0; goto out; } @@ -1674,26 +1681,32 @@ static int cred_has_capability(const struct cred *cred, return rc; } -/* Check whether a task has a particular permission to an inode. - The 'adp' parameter is optional and allows other audit - data to be passed (e.g. the dentry). */ -static int inode_has_perm(const struct cred *cred, - struct inode *inode, - u32 perms, - struct common_audit_data *adp) +/* + * Check whether a SID has a particular permission to an inode. The 'adp' + * parameter is optional and allows other audit data to be passed (e.g. the + * dentry). + */ +static int inode_sid_has_perm(u32 sid, struct inode *inode, u32 perms, + struct common_audit_data *adp) { struct inode_security_struct *isec; - u32 sid; if (unlikely(IS_PRIVATE(inode))) return 0; - sid = cred_sid(cred); isec = selinux_inode(inode); return avc_has_perm(sid, isec->sid, isec->sclass, perms, adp); } +static int inode_has_perm(const struct cred *cred, + struct inode *inode, + u32 perms, + struct common_audit_data *adp) +{ + return inode_sid_has_perm(cred_sid(cred), inode, perms, adp); +} + /* Same as inode_has_perm, but pass explicit audit data containing the dentry to help the auditing code to more easily generate the pathname if needed. */ @@ -3074,12 +3087,14 @@ static int selinux_inode_init_security_anon(struct inode *inode, &ad); } -static int selinux_inode_create(struct inode *dir, struct dentry *dentry, umode_t mode) +static int selinux_inode_create(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) { return may_create(dir, dentry, SECCLASS_FILE); } -static int selinux_inode_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) +static int selinux_inode_link(const struct mnt_idmap *idmap, struct dentry *old_dentry, + struct inode *dir, struct dentry *new_dentry) { return may_link(dir, old_dentry, MAY_LINK); } @@ -3089,12 +3104,14 @@ static int selinux_inode_unlink(struct inode *dir, struct dentry *dentry) return may_link(dir, dentry, MAY_UNLINK); } -static int selinux_inode_symlink(struct inode *dir, struct dentry *dentry, const char *name) +static int selinux_inode_symlink(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *name) { return may_create(dir, dentry, SECCLASS_LNK_FILE); } -static int selinux_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mask) +static int selinux_inode_mkdir(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mask) { return may_create(dir, dentry, SECCLASS_DIR); } @@ -3104,7 +3121,8 @@ static int selinux_inode_rmdir(struct inode *dir, struct dentry *dentry) return may_link(dir, dentry, MAY_RMDIR); } -static int selinux_inode_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) +static int selinux_inode_mknod(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) { return may_create(dir, dentry, inode_mode_to_security_class(mode)); } @@ -3236,13 +3254,15 @@ static inline void task_avdcache_update(struct task_security_struct *tsec, /** * selinux_inode_permission - Check if the current task can access an inode + * @idmap: idmap of the mount * @inode: the inode that is being accessed * @requested: the accesses being requested * * Check if the current task is allowed to access @inode according to * @requested. Returns 0 if allowed, negative values otherwise. */ -static int selinux_inode_permission(struct inode *inode, int requested) +static int selinux_inode_permission(const struct mnt_idmap *idmap, + struct inode *inode, int requested) { int mask; u32 perms; @@ -3297,7 +3317,7 @@ static int selinux_inode_permission(struct inode *inode, int requested) return rc; } -static int selinux_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int selinux_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { const struct cred *cred = current_cred(); @@ -3367,7 +3387,7 @@ static int selinux_inode_xattr_skipcap(const char *name) return !strcmp(name, XATTR_NAME_SELINUX); } -static int selinux_inode_setxattr(struct mnt_idmap *idmap, +static int selinux_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -3453,20 +3473,20 @@ static int selinux_inode_setxattr(struct mnt_idmap *idmap, &ad); } -static int selinux_inode_set_acl(struct mnt_idmap *idmap, +static int selinux_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); } -static int selinux_inode_get_acl(struct mnt_idmap *idmap, +static int selinux_inode_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return dentry_has_perm(current_cred(), dentry, FILE__GETATTR); } -static int selinux_inode_remove_acl(struct mnt_idmap *idmap, +static int selinux_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { return dentry_has_perm(current_cred(), dentry, FILE__SETATTR); @@ -3526,7 +3546,7 @@ static int selinux_inode_listxattr(struct dentry *dentry) return dentry_has_perm(cred, dentry, FILE__GETATTR); } -static int selinux_inode_removexattr(struct mnt_idmap *idmap, +static int selinux_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { /* if not a selinux xattr, only check the ordinary setattr perm */ @@ -3606,7 +3626,7 @@ static int selinux_path_notify(const struct path *path, u64 mask, * * Permission check is handled by selinux_inode_getxattr hook. */ -static int selinux_inode_getsecurity(struct mnt_idmap *idmap, +static int selinux_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) { @@ -3843,23 +3863,80 @@ static int selinux_file_alloc_security(struct file *file) return 0; } +static inline u32 selinux_file_user_sid(const struct file *file) +{ + if (unlikely(file->f_mode & FMODE_BACKING)) + return selinux_backing_file(file)->uf_sid; + return selinux_file(file)->sid; +} + static int selinux_backing_file_alloc(struct file *backing_file, const struct file *user_file) { struct backing_file_security_struct *bfsec; + const struct backing_file_security_struct *ubfsec; + struct backing_file_security_layer *layer; + u32 i; bfsec = selinux_backing_file(backing_file); - bfsec->uf_sid = selinux_file(user_file)->sid; + bfsec->uf_sid = selinux_file_user_sid(user_file); + if (!(user_file->f_mode & FMODE_BACKING)) + return 0; + + ubfsec = selinux_backing_file(user_file); + /* a wrapped count would make kmalloc_array() return ZERO_SIZE_PTR */ + if (unlikely(ubfsec->layer_count == U32_MAX)) + return -EOVERFLOW; + + /* + * The final VMA only retains the lowest backing file, so record the + * whole chain here rather than in the mmap hook, where concurrent + * mappings would have to be serialized. Size it dynamically: erofs + * inode sharing adds a backing file without bumping s_stack_depth. + */ + bfsec->layers = kmalloc_array(ubfsec->layer_count + 1, + sizeof(*bfsec->layers), GFP_KERNEL); + if (!bfsec->layers) + return -ENOMEM; + + for (i = 0; i < ubfsec->layer_count; i++) { + layer = &bfsec->layers[i]; + *layer = ubfsec->layers[i]; + path_get(&layer->path); + } + + /* f_path, not file_user_path(): this layer, not the top-level file */ + layer = &bfsec->layers[i]; + layer->path = user_file->f_path; + layer->mounter_sid = cred_sid(user_file->f_cred); + layer->fd_sid = selinux_file(user_file)->sid; + path_get(&layer->path); + bfsec->layer_count = ubfsec->layer_count + 1; return 0; } +static void selinux_backing_file_free(struct file *backing_file) +{ + struct backing_file_security_struct *bfsec; + + /* security_backing_file_free() may be called twice after an error */ + if (!backing_file_security(backing_file)) + return; + + bfsec = selinux_backing_file(backing_file); + while (bfsec->layer_count) + path_put(&bfsec->layers[--bfsec->layer_count].path); + kfree(bfsec->layers); + bfsec->layers = NULL; +} + /* * Check whether a task has the ioctl permission and cmd * operation to an inode. */ static int ioctl_has_perm(const struct cred *cred, struct file *file, - u32 requested, u16 cmd) + u32 requested, unsigned int cmd) { struct common_audit_data ad; struct file_security_struct *fsec = selinux_file(file); @@ -3930,14 +4007,14 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd, case FIOCLEX: case FIONCLEX: if (!selinux_policycap_ioctl_skip_cloexec()) - error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd); + error = ioctl_has_perm(cred, file, FILE__IOCTL, cmd); break; /* default case assumes that the command will go * to the file's ioctl() function. */ default: - error = ioctl_has_perm(cred, file, FILE__IOCTL, (u16) cmd); + error = ioctl_has_perm(cred, file, FILE__IOCTL, cmd); } return error; } @@ -3971,6 +4048,53 @@ static int selinux_file_ioctl_compat(struct file *file, unsigned int cmd, static int default_noexec __ro_after_init; +static u32 file_map_prot_to_av(unsigned long prot, bool shared) +{ + u32 av = FILE__READ; + + if (shared && (prot & PROT_WRITE)) + av |= FILE__WRITE; + if (prot & PROT_EXEC) + av |= FILE__EXECUTE; + + return av; +} + +static int backing_mounters_has_perm(const struct file *file, u32 av) +{ + const struct backing_file_security_struct *bfsec; + const struct backing_file_security_layer *layer; + struct common_audit_data ad; + struct inode *inode; + u32 i; + int rc; + + if (WARN_ON_ONCE(!(file->f_mode & FMODE_BACKING))) + return -EIO; + + bfsec = selinux_backing_file(file); + for (i = 0; i < bfsec->layer_count; i++) { + layer = &bfsec->layers[i]; + inode = d_inode(layer->path.dentry); + + ad.type = LSM_AUDIT_DATA_PATH; + ad.u.path = layer->path; + + if (layer->mounter_sid != layer->fd_sid) { + rc = avc_has_perm(layer->mounter_sid, layer->fd_sid, + SECCLASS_FD, FD__USE, &ad); + if (rc) + return rc; + } + + rc = inode_sid_has_perm(layer->mounter_sid, inode, av, &ad); + if (rc) + return rc; + } + + return 0; +} + static int __file_map_prot_check(const struct file *file, unsigned long prot, bool shared, bool mounter_check, bool bf_user_file) @@ -4004,14 +4128,10 @@ static int __file_map_prot_check(const struct file *file, unsigned long prot, if (file) { const struct cred *cred = mounter_check ? file->f_cred : current_cred(); - /* "read" always possible, "write" only if shared */ - u32 av = FILE__READ; - if (shared && prot_write) - av |= FILE__WRITE; - if (prot_exec) - av |= FILE__EXECUTE; - return __file_has_perm(cred, file, av, bf_user_file); + return __file_has_perm(cred, file, + file_map_prot_to_av(prot, shared), + bf_user_file); } return 0; @@ -4106,6 +4226,7 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, int rc; const struct cred *cred = current_cred(); u32 sid = cred_sid(cred); + u32 av; const struct file *file = vma->vm_file; bool backing_file; bool shared = vma->vm_flags & VM_SHARED; @@ -4149,6 +4270,10 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, if (rc) return rc; if (backing_file) { + rc = backing_mounters_has_perm(file, + FILE__EXECMOD); + if (rc) + return rc; rc = file_has_perm(file->f_cred, file, FILE__EXECMOD); if (rc) @@ -4161,6 +4286,10 @@ static int selinux_file_mprotect(struct vm_area_struct *vma, if (rc) return rc; if (backing_file) { + av = file_map_prot_to_av(prot, shared); + rc = backing_mounters_has_perm(file, av); + if (rc) + return rc; rc = file_map_prot_check(file, prot, shared, true); if (rc) return rc; @@ -7267,24 +7396,6 @@ static int selinux_bpf_prog(struct bpf_prog *prog) BPF__PROG_RUN, NULL); } -static u32 selinux_bpffs_creator_sid(u32 fd) -{ - struct path path; - struct super_block *sb; - struct superblock_security_struct *sbsec; - - CLASS(fd, f)(fd); - - if (fd_empty(f)) - return SECSID_NULL; - - path = fd_file(f)->f_path; - sb = path.dentry->d_sb; - sbsec = selinux_superblock(sb); - - return sbsec->creator_sid; -} - static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, struct bpf_token *token, bool kernel) { @@ -7297,7 +7408,7 @@ static int selinux_bpf_map_create(struct bpf_map *map, union bpf_attr *attr, if (!token) ssid = bpfsec->sid; else - ssid = selinux_bpffs_creator_sid(attr->map_token_fd); + ssid = selinux_bpf_token_security(token)->grantor_sid; return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__MAP_CREATE, NULL); @@ -7315,7 +7426,7 @@ static int selinux_bpf_prog_load(struct bpf_prog *prog, union bpf_attr *attr, if (!token) ssid = bpfsec->sid; else - ssid = selinux_bpffs_creator_sid(attr->prog_token_fd); + ssid = selinux_bpf_token_security(token)->grantor_sid; return avc_has_perm(ssid, bpfsec->sid, SECCLASS_BPF, BPF__PROG_LOAD, NULL); @@ -7329,12 +7440,14 @@ static int selinux_bpf_token_create(struct bpf_token *token, const struct path *path) { struct bpf_security_struct *bpfsec; - u32 sid = selinux_bpffs_creator_sid(attr->token_create.bpffs_fd); + struct superblock_security_struct *sbsec; int err; + sbsec = selinux_superblock(path->dentry->d_sb); + bpfsec = selinux_bpf_token_security(token); bpfsec->sid = current_sid(); - bpfsec->grantor_sid = sid; + bpfsec->grantor_sid = sbsec->creator_sid; bpfsec->perms = 0; /** @@ -7343,15 +7456,15 @@ static int selinux_bpf_token_create(struct bpf_token *token, * in the allowed_cmds bitmap. */ if (bpf_token_cmd(token, BPF_MAP_CREATE)) { - err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, - BPF__MAP_CREATE_AS, NULL); + err = avc_has_perm(bpfsec->sid, bpfsec->grantor_sid, + SECCLASS_BPF, BPF__MAP_CREATE_AS, NULL); if (err) return err; bpfsec->perms |= BPF__MAP_CREATE; } if (bpf_token_cmd(token, BPF_PROG_LOAD)) { - err = avc_has_perm(bpfsec->sid, sid, SECCLASS_BPF, - BPF__PROG_LOAD_AS, NULL); + err = avc_has_perm(bpfsec->sid, bpfsec->grantor_sid, + SECCLASS_BPF, BPF__PROG_LOAD_AS, NULL); if (err) return err; bpfsec->perms |= BPF__PROG_LOAD; @@ -7635,6 +7748,7 @@ static struct security_hook_list selinux_hooks[] __ro_after_init = { LSM_HOOK_INIT(file_permission, selinux_file_permission), LSM_HOOK_INIT(file_alloc_security, selinux_file_alloc_security), LSM_HOOK_INIT(backing_file_alloc, selinux_backing_file_alloc), + LSM_HOOK_INIT(backing_file_free, selinux_backing_file_free), LSM_HOOK_INIT(file_ioctl, selinux_file_ioctl), LSM_HOOK_INIT(file_ioctl_compat, selinux_file_ioctl_compat), LSM_HOOK_INIT(mmap_file, selinux_mmap_file), diff --git a/security/selinux/include/avc.h b/security/selinux/include/avc.h index 01b5167fee1a..87027a934a11 100644 --- a/security/selinux/include/avc.h +++ b/security/selinux/include/avc.h @@ -61,8 +61,9 @@ struct selinux_audit_data { void __init avc_init(void); -static inline u32 avc_audit_required(u32 requested, struct av_decision *avd, - int result, u32 auditdeny, u32 *deniedp) +static inline u32 avc_audit_required(u32 requested, + const struct av_decision *avd, int result, + u32 auditdeny, u32 *deniedp) { u32 denied, audited; @@ -121,7 +122,7 @@ int slow_avc_audit(u32 ssid, u32 tsid, u16 tclass, u32 requested, u32 audited, * before calling the auditing code. */ static inline int avc_audit(u32 ssid, u32 tsid, u16 tclass, u32 requested, - struct av_decision *avd, int result, + const struct av_decision *avd, int result, struct common_audit_data *a) { u32 audited, denied; diff --git a/security/selinux/include/objsec.h b/security/selinux/include/objsec.h index 3c0a16ec978b..2f21568251ff 100644 --- a/security/selinux/include/objsec.h +++ b/security/selinux/include/objsec.h @@ -86,8 +86,16 @@ struct file_security_struct { u32 pseqno; /* Policy seqno at the time of file open */ }; +struct backing_file_security_layer { + struct path path; /* this layer's real path */ + u32 mounter_sid; /* SID of the mounter that opened it */ + u32 fd_sid; /* SID of its open file description */ +}; + struct backing_file_security_struct { - u32 uf_sid; /* associated user file fsec->sid */ + u32 uf_sid; /* top-level user file fsec->sid */ + u32 layer_count; /* number of intermediate backing files */ + struct backing_file_security_layer *layers; }; struct superblock_security_struct { diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c index c7d91476971c..4256838d496c 100644 --- a/security/selinux/selinuxfs.c +++ b/security/selinux/selinuxfs.c @@ -75,8 +75,8 @@ struct selinux_fs_info { char **bool_pending_names; int *bool_pending_values; struct dentry *class_dir; - unsigned long last_class_ino; - unsigned long last_ino; + u64 last_class_ino; + u64 last_ino; struct super_block *sb; }; @@ -247,11 +247,6 @@ static int sel_mmap_handle_status(struct file *filp, /* only allows one page from the head */ if (vma->vm_pgoff > 0 || size != PAGE_SIZE) return -EIO; - /* disallow writable mapping */ - if (vma->vm_flags & VM_WRITE) - return -EPERM; - /* disallow mprotect() turns it into writable */ - vm_flags_clear(vma, VM_MAYWRITE); return remap_pfn_range(vma, vma->vm_start, page_to_pfn(status), @@ -304,15 +299,14 @@ static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_ int **bool_pending_values); static int sel_make_classes(struct selinux_policy *newpolicy, struct dentry *class_dir, - unsigned long *last_class_ino); + u64 *last_class_ino); /* declaration for sel_make_class_dirs */ static struct dentry *sel_make_dir(struct dentry *dir, const char *name, - unsigned long *ino); + u64 *ino); /* declaration for sel_make_policy_nodes */ -static struct dentry *sel_make_swapover_dir(struct super_block *sb, - unsigned long *ino); +static struct dentry *sel_make_swapover_dir(struct super_block *sb, u64 *ino); static ssize_t sel_read_mls(struct file *filp, char __user *buf, size_t count, loff_t *ppos) @@ -467,7 +461,7 @@ static int sel_make_policy_nodes(struct selinux_fs_info *fsi, unsigned int bool_num = 0; char **bool_names = NULL; int *bool_values = NULL; - unsigned long tmp_ino = fsi->last_ino; /* Don't increment last_ino in this function */ + u64 tmp_ino = fsi->last_ino; /* Don't increment last_ino in this function */ tmp_parent = sel_make_swapover_dir(fsi->sb, &tmp_ino); if (IS_ERR(tmp_parent)) @@ -1568,30 +1562,32 @@ static int sel_make_initcon_files(struct dentry *dir) return err; } -static inline unsigned long sel_class_to_ino(u16 class) +static inline u64 sel_class_to_ino(u16 class) { return (class * (SEL_VEC_MAX + 1)) | SEL_CLASS_INO_OFFSET; } -static inline u16 sel_ino_to_class(unsigned long ino) +static inline u16 sel_ino_to_class(u64 ino) { - return (ino & SEL_INO_MASK) / (SEL_VEC_MAX + 1); + u32 ino_masked = ino & SEL_INO_MASK; + return ino_masked / (SEL_VEC_MAX + 1); } -static inline unsigned long sel_perm_to_ino(u16 class, u32 perm) +static inline u64 sel_perm_to_ino(u16 class, u32 perm) { return (class * (SEL_VEC_MAX + 1) + perm) | SEL_CLASS_INO_OFFSET; } -static inline u32 sel_ino_to_perm(unsigned long ino) +static inline u32 sel_ino_to_perm(u64 ino) { - return (ino & SEL_INO_MASK) % (SEL_VEC_MAX + 1); + u32 ino_masked = ino & SEL_INO_MASK; + return ino_masked % (SEL_VEC_MAX + 1); } static ssize_t sel_read_class(struct file *file, char __user *buf, size_t count, loff_t *ppos) { - unsigned long ino = file_inode(file)->i_ino; + u64 ino = file_inode(file)->i_ino; char res[TMPBUFLEN]; ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_class(ino)); return simple_read_from_buffer(buf, count, ppos, res, len); @@ -1605,7 +1601,7 @@ static const struct file_operations sel_class_ops = { static ssize_t sel_read_perm(struct file *file, char __user *buf, size_t count, loff_t *ppos) { - unsigned long ino = file_inode(file)->i_ino; + u64 ino = file_inode(file)->i_ino; char res[TMPBUFLEN]; ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_perm(ino)); return simple_read_from_buffer(buf, count, ppos, res, len); @@ -1622,7 +1618,7 @@ static ssize_t sel_read_policycap(struct file *file, char __user *buf, int value; char tmpbuf[TMPBUFLEN]; ssize_t length; - unsigned long i_ino = file_inode(file)->i_ino; + u64 i_ino = file_inode(file)->i_ino; value = security_policycap_supported(i_ino & SEL_INO_MASK); length = scnprintf(tmpbuf, TMPBUFLEN, "%d", value); @@ -1698,7 +1694,7 @@ static int sel_make_class_dir_entries(struct selinux_policy *newpolicy, static int sel_make_classes(struct selinux_policy *newpolicy, struct dentry *class_dir, - unsigned long *last_class_ino) + u64 *last_class_ino) { u32 i, nclasses; int rc; @@ -1763,7 +1759,7 @@ static int sel_make_policycap(struct dentry *dir) } static struct dentry *sel_make_dir(struct dentry *dir, const char *name, - unsigned long *ino) + u64 *ino) { struct inode *inode; @@ -1782,7 +1778,7 @@ static struct dentry *sel_make_dir(struct dentry *dir, const char *name, return sel_attach(dir, name, inode); } -static int reject_all(struct mnt_idmap *idmap, struct inode *inode, int mask) +static int reject_all(const struct mnt_idmap *idmap, struct inode *inode, int mask) { return -EPERM; // no access for anyone, root or no root. } @@ -1792,8 +1788,7 @@ static const struct inode_operations swapover_dir_inode_operations = { .permission = reject_all, }; -static struct dentry *sel_make_swapover_dir(struct super_block *sb, - unsigned long *ino) +static struct dentry *sel_make_swapover_dir(struct super_block *sb, u64 *ino) { struct dentry *dentry; struct inode *inode; @@ -1820,6 +1815,17 @@ static struct dentry *sel_make_swapover_dir(struct super_block *sb, #define NULL_FILE_NAME "null" +static void sel_mark_immutable(struct dentry *root, const char *name) +{ + struct qstr q = QSTR(name); + struct dentry *dentry = try_lookup_noperm(&q, root); + + if (!IS_ERR_OR_NULL(dentry)) { + d_inode(dentry)->i_flags |= S_IMMUTABLE; + dput(dentry); + } +} + static int sel_fill_super(struct super_block *sb, struct fs_context *fc) { struct selinux_fs_info *fsi; @@ -1859,6 +1865,9 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc) if (ret) goto err; + sel_mark_immutable(sb->s_root, "status"); + sel_mark_immutable(sb->s_root, "policy"); + fsi = sb->s_fs_info; fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino); if (IS_ERR(fsi->bool_dir)) { diff --git a/security/selinux/ss/mls.c b/security/selinux/ss/mls.c index 40c62600650e..b48f8256dd4c 100644 --- a/security/selinux/ss/mls.c +++ b/security/selinux/ss/mls.c @@ -400,44 +400,6 @@ int mls_range_set(struct context *context, struct mls_range *range) return rc; } -int mls_setup_user_range(struct policydb *p, struct context *fromcon, - struct user_datum *user, struct context *usercon) -{ - if (p->mls_enabled) { - struct mls_level *fromcon_sen = &(fromcon->range.level[0]); - struct mls_level *fromcon_clr = &(fromcon->range.level[1]); - struct mls_level *user_low = &(user->range.level[0]); - struct mls_level *user_clr = &(user->range.level[1]); - struct mls_level *user_def = &(user->dfltlevel); - struct mls_level *usercon_sen = &(usercon->range.level[0]); - struct mls_level *usercon_clr = &(usercon->range.level[1]); - - /* Honor the user's default level if we can */ - if (mls_level_between(user_def, fromcon_sen, fromcon_clr)) - *usercon_sen = *user_def; - else if (mls_level_between(fromcon_sen, user_def, user_clr)) - *usercon_sen = *fromcon_sen; - else if (mls_level_between(fromcon_clr, user_low, user_def)) - *usercon_sen = *user_low; - else - return -EINVAL; - - /* Lower the clearance of available contexts - if the clearance of "fromcon" is lower than - that of the user's default clearance (but - only if the "fromcon" clearance dominates - the user's computed sensitivity level) */ - if (mls_level_dom(user_clr, fromcon_clr)) - *usercon_clr = *fromcon_clr; - else if (mls_level_dom(fromcon_clr, user_clr)) - *usercon_clr = *user_clr; - else - return -EINVAL; - } - - return 0; -} - /* * Convert the MLS fields in the security context * structure `oldc' from the values specified in the diff --git a/security/selinux/ss/mls.h b/security/selinux/ss/mls.h index 93cde1b22992..c107b5182fb7 100644 --- a/security/selinux/ss/mls.h +++ b/security/selinux/ss/mls.h @@ -46,9 +46,6 @@ int mls_compute_sid(struct policydb *p, struct context *scontext, struct context *tcontext, u16 tclass, u32 specified, struct context *newcontext, bool sock); -int mls_setup_user_range(struct policydb *p, struct context *fromcon, - struct user_datum *user, struct context *usercon); - #ifdef CONFIG_NETLABEL void mls_export_netlbl_lvl(struct policydb *p, struct context *context, struct netlbl_lsm_secattr *secattr); diff --git a/security/selinux/ss/mls_types.h b/security/selinux/ss/mls_types.h index 51df2ebd1211..d36a03f104c2 100644 --- a/security/selinux/ss/mls_types.h +++ b/security/selinux/ss/mls_types.h @@ -42,9 +42,6 @@ static inline int mls_level_dom(const struct mls_level *l1, #define mls_level_incomp(l1, l2) \ (!mls_level_dom((l1), (l2)) && !mls_level_dom((l2), (l1))) -#define mls_level_between(l1, l2, l3) \ - (mls_level_dom((l1), (l2)) && mls_level_dom((l3), (l1))) - #define mls_range_contains(r1, r2) \ (mls_level_dom(&(r2).level[0], &(r1).level[0]) && \ mls_level_dom(&(r1).level[1], &(r2).level[1])) diff --git a/security/selinux/ss/policydb.c b/security/selinux/ss/policydb.c index b75c977c6673..fe0c0792583f 100644 --- a/security/selinux/ss/policydb.c +++ b/security/selinux/ss/policydb.c @@ -560,6 +560,9 @@ static int common_index(void *key, void *datum, void *datap) if (!comdatum->value || comdatum->value > p->p_commons.nprim) return -EINVAL; + if (p->sym_val_to_name[SYM_COMMONS][comdatum->value - 1]) + return -EINVAL; + p->sym_val_to_name[SYM_COMMONS][comdatum->value - 1] = key; return 0; @@ -575,6 +578,9 @@ static int class_index(void *key, void *datum, void *datap) if (!cladatum->value || cladatum->value > p->p_classes.nprim) return -EINVAL; + if (p->class_val_to_struct[cladatum->value - 1]) + return -EINVAL; + p->sym_val_to_name[SYM_CLASSES][cladatum->value - 1] = key; p->class_val_to_struct[cladatum->value - 1] = cladatum; return 0; @@ -591,6 +597,9 @@ static int role_index(void *key, void *datum, void *datap) role->bounds > p->p_roles.nprim) return -EINVAL; + if (p->role_val_to_struct[role->value - 1]) + return -EINVAL; + p->sym_val_to_name[SYM_ROLES][role->value - 1] = key; p->role_val_to_struct[role->value - 1] = role; return 0; @@ -613,6 +622,8 @@ static int type_index(void *key, void *datum, void *datap) } if (typdatum->primary) { + if (p->type_val_to_struct[typdatum->value - 1]) + return -EINVAL; p->sym_val_to_name[SYM_TYPES][typdatum->value - 1] = key; p->type_val_to_struct[typdatum->value - 1] = typdatum; } @@ -631,6 +642,9 @@ static int user_index(void *key, void *datum, void *datap) usrdatum->bounds > p->p_users.nprim) return -EINVAL; + if (p->user_val_to_struct[usrdatum->value - 1]) + return -EINVAL; + p->sym_val_to_name[SYM_USERS][usrdatum->value - 1] = key; p->user_val_to_struct[usrdatum->value - 1] = usrdatum; return 0; @@ -647,8 +661,11 @@ static int sens_index(void *key, void *datum, void *datap) if (!levdatum->level.sens || levdatum->level.sens > p->p_levels.nprim) return -EINVAL; - if (!levdatum->isalias) + if (!levdatum->isalias) { + if (p->sym_val_to_name[SYM_LEVELS][levdatum->level.sens - 1]) + return -EINVAL; p->sym_val_to_name[SYM_LEVELS][levdatum->level.sens - 1] = key; + } return 0; } @@ -664,8 +681,11 @@ static int cat_index(void *key, void *datum, void *datap) if (!catdatum->value || catdatum->value > p->p_cats.nprim) return -EINVAL; - if (!catdatum->isalias) + if (!catdatum->isalias) { + if (p->sym_val_to_name[SYM_CATS][catdatum->value - 1]) + return -EINVAL; p->sym_val_to_name[SYM_CATS][catdatum->value - 1] = key; + } return 0; } @@ -687,6 +707,64 @@ static int sens_cat_index_check(void *key, void *datum, void *datap) return 0; } +static int role_index_check(void *key, void *datum, void *datap) +{ + const struct policydb *p = datap; + const struct role_datum *role = datum; + struct ebitmap_node *node; + u32 bit; + + ebitmap_for_each_positive_bit(&role->dominates, node, bit) { + if (!policydb_role_isvalid(p, bit + 1)) { + pr_err("SELinux: role %s declares dominance on undefined role %u\n", + (const char *)key, bit + 1); + return -EINVAL; + } + } + + ebitmap_for_each_positive_bit(&role->types, node, bit) { + if (!policydb_type_isvalid(p, bit + 1)) { + pr_err("SELinux: role %s authorizes undefined type %u\n", + (const char *)key, bit + 1); + return -EINVAL; + } + } + + return 0; +} + +static int user_index_check(void *key, void *datum, void *datap) +{ + const struct policydb *p = datap; + const struct user_datum *usr = datum; + struct ebitmap_node *node; + u32 bit; + + ebitmap_for_each_positive_bit(&usr->roles, node, bit) { + if (!policydb_role_isvalid(p, bit + 1)) { + pr_err("SELinux: user %s authorizes undefined role %u\n", + (const char *)key, bit + 1); + return -EINVAL; + } + } + + if (p->mls_enabled) { + if (!mls_range_isvalid(p, &usr->range)) { + pr_err("SELinux: user %s has an invalid MLS range\n", + (const char *)key); + return -EINVAL; + } + + if (!mls_level_isvalid(p, &usr->dfltlevel)) { + pr_err("SELinux: user %s has an invalid MLS default level\n", + (const char *)key); + return -EINVAL; + } + } + + return 0; +} + /* clang-format off */ static int (*const index_f[SYM_NUM])(void *key, void *datum, void *datap) = { common_index, @@ -740,8 +818,9 @@ static inline void symtab_hash_eval(struct symtab *s) */ static int policydb_index(struct policydb *p) { + struct ebitmap_node *node; int i, rc; - u32 v; + u32 bit, v; if (p->mls_enabled) pr_debug( @@ -816,6 +895,31 @@ static int policydb_index(struct policydb *p) goto out; } + rc = hashtab_map(&p->p_roles.table, role_index_check, p); + if (rc) + goto out; + + rc = hashtab_map(&p->p_users.table, user_index_check, p); + if (rc) + goto out; + + ebitmap_for_each_positive_bit(&p->permissive_map, node, bit) { + if (!policydb_simpletype_isvalid(p, bit)) { + pr_err("SELinux: permissive map refers to invalid type %u\n", + bit); + rc = -EINVAL; + goto out; + } + } + ebitmap_for_each_positive_bit(&p->neveraudit_map, node, bit) { + if (!policydb_simpletype_isvalid(p, bit)) { + pr_err("SELinux: neveraudit map refers to invalid type %u\n", + bit); + rc = -EINVAL; + goto out; + } + } + rc = 0; out: return rc; @@ -1037,13 +1141,13 @@ bool policydb_context_isvalid(const struct policydb *p, const struct context *c) const struct role_datum *role; const struct user_datum *usrdatum; - if (!c->role || c->role > p->p_roles.nprim) + if (!policydb_role_isvalid(p, c->role)) return false; - if (!c->user || c->user > p->p_users.nprim) + if (!policydb_user_isvalid(p, c->user)) return false; - if (!c->type || c->type > p->p_types.nprim) + if (!policydb_simpletype_isvalid(p, c->type)) return false; if (c->role != OBJECT_R_VAL) { @@ -1051,7 +1155,7 @@ bool policydb_context_isvalid(const struct policydb *p, const struct context *c) * Role must be authorized for the type. */ role = p->role_val_to_struct[c->role - 1]; - if (!role || !ebitmap_get_bit(&role->types, c->type - 1)) + if (!ebitmap_get_bit(&role->types, c->type - 1)) /* role may not be associated with type */ return false; @@ -1059,9 +1163,6 @@ bool policydb_context_isvalid(const struct policydb *p, const struct context *c) * User must be authorized for the role. */ usrdatum = p->user_val_to_struct[c->user - 1]; - if (!usrdatum) - return false; - if (!ebitmap_get_bit(&usrdatum->roles, c->role - 1)) /* user may not be associated with role */ return false; @@ -2350,6 +2451,10 @@ static int filename_trans_read_helper(struct policydb *p, struct policy_file *fp if (rc) goto out; + rc = -EINVAL; + if (ebitmap_get_highest_set_bit(&datum->stypes) >= p->p_types.nprim) + goto out; + rc = next_entry(buf, fp, sizeof(u32)); if (rc) goto out; @@ -2847,7 +2952,8 @@ int policydb_read(struct policydb *p, struct policy_file *fp) goto bad; } - if ((le32_to_cpu(buf[1]) & POLICYDB_CONFIG_MLS)) { + val = le32_to_cpu(buf[1]); + if (val & POLICYDB_CONFIG_MLS) { p->mls_enabled = 1; rc = -EINVAL; @@ -2858,8 +2964,13 @@ int policydb_read(struct policydb *p, struct policy_file *fp) goto bad; } } - p->reject_unknown = !!(le32_to_cpu(buf[1]) & REJECT_UNKNOWN); - p->allow_unknown = !!(le32_to_cpu(buf[1]) & ALLOW_UNKNOWN); + rc = -EINVAL; + if ((val & (REJECT_UNKNOWN | ALLOW_UNKNOWN)) == (REJECT_UNKNOWN | ALLOW_UNKNOWN)) { + pr_err("SELinux: policydb configuration both rejects and allows unknown classes and permissions\n"); + goto bad; + } + p->reject_unknown = !!(val & REJECT_UNKNOWN); + p->allow_unknown = !!(val & ALLOW_UNKNOWN); if (p->policyvers >= POLICYDB_VERSION_POLCAP) { rc = ebitmap_read(&p->policycaps, fp); diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 8e88ac65fd7f..28a8e12603ce 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1089,14 +1089,15 @@ instant_inode: /** * smack_inode_link - Smack check on link + * @idmap: idmap of the mount * @old_dentry: the existing object * @dir: unused * @new_dentry: the new object * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_link(struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry) +static int smack_inode_link(const struct mnt_idmap *idmap, struct dentry *old_dentry, + struct inode *dir, struct dentry *new_dentry) { struct smack_known *isp; struct smk_audit_info ad; @@ -1226,6 +1227,7 @@ static int smack_inode_rename(struct inode *old_inode, /** * smack_inode_permission - Smack version of permission() + * @idmap: idmap of the mount * @inode: the inode in question * @mask: the access requested * @@ -1233,7 +1235,8 @@ static int smack_inode_rename(struct inode *old_inode, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_permission(struct inode *inode, int mask) +static int smack_inode_permission(const struct mnt_idmap *idmap, struct inode *inode, + int mask) { struct superblock_smack *sbsp = smack_superblock(inode->i_sb); struct smk_audit_info ad; @@ -1270,7 +1273,7 @@ static int smack_inode_permission(struct inode *inode, int mask) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_setattr(struct mnt_idmap *idmap, struct dentry *dentry, +static int smack_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *iattr) { struct smk_audit_info ad; @@ -1348,7 +1351,7 @@ static int smack_inode_xattr_skipcap(const char *name) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_setxattr(struct mnt_idmap *idmap, +static int smack_inode_setxattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { @@ -1482,7 +1485,7 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_removexattr(struct mnt_idmap *idmap, +static int smack_inode_removexattr(const struct mnt_idmap *idmap, struct dentry *dentry, const char *name) { struct inode_smack *isp; @@ -1543,7 +1546,7 @@ static int smack_inode_removexattr(struct mnt_idmap *idmap, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_set_acl(struct mnt_idmap *idmap, +static int smack_inode_set_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name, struct posix_acl *kacl) { @@ -1566,7 +1569,7 @@ static int smack_inode_set_acl(struct mnt_idmap *idmap, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_get_acl(struct mnt_idmap *idmap, +static int smack_inode_get_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { struct smk_audit_info ad; @@ -1588,7 +1591,7 @@ static int smack_inode_get_acl(struct mnt_idmap *idmap, * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_remove_acl(struct mnt_idmap *idmap, +static int smack_inode_remove_acl(const struct mnt_idmap *idmap, struct dentry *dentry, const char *acl_name) { struct smk_audit_info ad; @@ -1612,7 +1615,7 @@ static int smack_inode_remove_acl(struct mnt_idmap *idmap, * * Returns the size of the attribute or an error code */ -static int smack_inode_getsecurity(struct mnt_idmap *idmap, +static int smack_inode_getsecurity(const struct mnt_idmap *idmap, struct inode *inode, const char *name, void **buffer, bool alloc) { diff --git a/security/tomoyo/common.h b/security/tomoyo/common.h index d098cf8aae61..d26034000913 100644 --- a/security/tomoyo/common.h +++ b/security/tomoyo/common.h @@ -16,7 +16,7 @@ #include <linux/string.h> #include <linux/mm.h> #include <linux/file.h> -#include <linux/kmod.h> +#include <linux/umh.h> #include <linux/fs.h> #include <linux/sched.h> #include <linux/namei.h> |
