summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
Diffstat (limited to 'security')
-rw-r--r--security/Kconfig.hardening2
-rw-r--r--security/apparmor/apparmorfs.c2
-rw-r--r--security/apparmor/lsm.c4
-rw-r--r--security/commoncap.c10
-rw-r--r--security/integrity/evm/evm_main.c35
-rw-r--r--security/integrity/ima/Kconfig6
-rw-r--r--security/integrity/ima/ima.h10
-rw-r--r--security/integrity/ima/ima_api.c10
-rw-r--r--security/integrity/ima/ima_appraise.c20
-rw-r--r--security/integrity/ima/ima_fs.c7
-rw-r--r--security/integrity/ima/ima_init.c2
-rw-r--r--security/integrity/ima/ima_main.c19
-rw-r--r--security/integrity/ima/ima_policy.c22
-rw-r--r--security/integrity/ima/ima_queue.c5
-rw-r--r--security/integrity/ima/ima_queue_keys.c8
-rw-r--r--security/integrity/ima/ima_template.c12
-rw-r--r--security/integrity/ima/ima_template_lib.c2
-rw-r--r--security/integrity/integrity.h18
-rw-r--r--security/integrity/integrity_audit.c12
-rw-r--r--security/ipe/audit.c52
-rw-r--r--security/ipe/eval.c12
-rw-r--r--security/ipe/eval.h2
-rw-r--r--security/ipe/fs.c8
-rw-r--r--security/ipe/hooks.c22
-rw-r--r--security/ipe/policy.c2
-rw-r--r--security/ipe/policy_fs.c3
-rw-r--r--security/ipe/policy_parser.c10
-rw-r--r--security/keys/encrypted-keys/encrypted.c20
-rw-r--r--security/keys/gc.c4
-rw-r--r--security/keys/key.c19
-rw-r--r--security/keys/keyctl.c15
-rw-r--r--security/keys/keyring.c152
-rw-r--r--security/keys/persistent.c55
-rw-r--r--security/keys/proc.c4
-rw-r--r--security/keys/process_keys.c7
-rw-r--r--security/keys/request_key.c2
-rw-r--r--security/keys/request_key_auth.c12
-rw-r--r--security/keys/trusted-keys/trusted_tpm1.c156
-rw-r--r--security/keys/trusted-keys/trusted_tpm2.c106
-rw-r--r--security/landlock/.kunitconfig2
-rw-r--r--security/landlock/Makefile4
-rw-r--r--security/landlock/access.h69
-rw-r--r--security/landlock/audit.c32
-rw-r--r--security/landlock/cap.c159
-rw-r--r--security/landlock/cap.h48
-rw-r--r--security/landlock/cred.h2
-rw-r--r--security/landlock/domain.c16
-rw-r--r--security/landlock/domain.h103
-rw-r--r--security/landlock/fs.c40
-rw-r--r--security/landlock/limits.h9
-rw-r--r--security/landlock/log.c53
-rw-r--r--security/landlock/log.h62
-rw-r--r--security/landlock/net.c44
-rw-r--r--security/landlock/ns.c241
-rw-r--r--security/landlock/ns.h18
-rw-r--r--security/landlock/ruleset.c29
-rw-r--r--security/landlock/ruleset.h38
-rw-r--r--security/landlock/setup.c4
-rw-r--r--security/landlock/syscalls.c217
-rw-r--r--security/landlock/task.c87
-rw-r--r--security/landlock/trace.c278
-rw-r--r--security/lsm_audit.c8
-rw-r--r--security/lsm_init.c9
-rw-r--r--security/security.c139
-rw-r--r--security/selinux/avc.c47
-rw-r--r--security/selinux/hooks.c234
-rw-r--r--security/selinux/include/avc.h7
-rw-r--r--security/selinux/include/objsec.h10
-rw-r--r--security/selinux/selinuxfs.c61
-rw-r--r--security/selinux/ss/mls.c38
-rw-r--r--security/selinux/ss/mls.h3
-rw-r--r--security/selinux/ss/mls_types.h3
-rw-r--r--security/selinux/ss/policydb.c137
-rw-r--r--security/smack/smack_lsm.c23
-rw-r--r--security/tomoyo/common.h2
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(&notif_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>