diff options
33 files changed, 618 insertions, 130 deletions
diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst index b44ef68f6e4d..9518495edfbc 100644 --- a/Documentation/admin-guide/LSM/index.rst +++ b/Documentation/admin-guide/LSM/index.rst @@ -6,9 +6,11 @@ The Linux Security Module (LSM) framework provides a mechanism for various security checks to be hooked by new kernel extensions. The name "module" is a bit of a misnomer since these extensions are not actually loadable kernel modules. Instead, they are selectable at build-time via -CONFIG_DEFAULT_SECURITY and can be overridden at boot-time via the -``"security=..."`` kernel command line argument, in the case where multiple -LSMs were built into a given kernel. +CONFIG_LSM, an ordered list of the LSMs to enable, and can be +overridden at boot-time via the ``"lsm=..."`` kernel command line +argument. The ``"security=..."`` kernel command line argument remains +available to choose a legacy "major" security module, but has been +deprecated by the ``"lsm=..."`` parameter. The primary users of the LSM interface are Mandatory Access Control (MAC) extensions which provide a comprehensive security policy. Examples @@ -25,9 +27,15 @@ man-pages project. A list of the active security modules can be found by reading ``/sys/kernel/security/lsm``. This is a comma separated list, and will always include the capability module. The list reflects the -order in which checks are made. The capability module will always -be first, followed by any "minor" modules (e.g. Yama) and then -the one "major" module (e.g. SELinux) if there is one configured. +order in which checks are made. The capability module will be +first, unless CONFIG_SECURITY_LOCKDOWN_LSM_EARLY is enabled, in +which case the lockdown module will precede it. The integrity +modules (e.g. IMA and EVM), if enabled in the kernel +configuration, are always placed at the end of the list. Any +other "minor" modules (e.g. Yama) and the one "major" module +(e.g. SELinux), if there is one configured, appear in between, +in the order given by CONFIG_LSM or the ``"lsm=..."`` kernel +command line parameter. Process attributes associated with "major" security modules should be accessed and maintained using the special files in ``/proc/.../attr``. diff --git a/fs/cachefiles/security.c b/fs/cachefiles/security.c index fc6611886b3b..eefe5453b904 100644 --- a/fs/cachefiles/security.c +++ b/fs/cachefiles/security.c @@ -51,14 +51,14 @@ static int cachefiles_check_cache_dir(struct cachefiles_cache *cache, { int ret; - ret = security_inode_mkdir(d_backing_inode(root), root, 0); + ret = security_inode_mkdir(&nop_mnt_idmap, d_backing_inode(root), root, 0); if (ret < 0) { pr_err("Security denies permission to make dirs: error %d", ret); return ret; } - ret = security_inode_create(d_backing_inode(root), root, 0); + ret = security_inode_create(&nop_mnt_idmap, d_backing_inode(root), root, 0); if (ret < 0) pr_err("Security denies permission to create files: error %d", ret); diff --git a/fs/namei.c b/fs/namei.c index 59c8a669081a..54ad61923548 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -658,7 +658,7 @@ int inode_permission(const struct mnt_idmap *idmap, if (unlikely(retval)) return retval; - return security_inode_permission(inode, mask); + return security_inode_permission(idmap, inode, mask); } EXPORT_SYMBOL(inode_permission); @@ -695,7 +695,7 @@ static __always_inline int lookup_inode_permission_may_exec(const struct mnt_idm if (unlikely(((inode->i_mode & 0111) != 0111) || !no_acl_inode(inode))) return inode_permission(idmap, inode, mask); - return security_inode_permission(inode, mask); + return security_inode_permission(idmap, inode, mask); } /** @@ -4188,7 +4188,7 @@ int vfs_create(const struct mnt_idmap *idmap, struct dentry *dentry, umode_t mod return -EACCES; /* shouldn't it be ENOSYS? */ mode = vfs_prepare_mode(idmap, dir, mode, S_IALLUGO, S_IFREG); - error = security_inode_create(dir, dentry, mode); + error = security_inode_create(idmap, dir, dentry, mode); if (error) return error; error = try_break_deleg(dir, LEASE_BREAK_DIR_CREATE, di); @@ -4212,7 +4212,7 @@ int vfs_mkobj(struct dentry *dentry, umode_t mode, mode &= S_IALLUGO; mode |= S_IFREG; - error = security_inode_create(dir, dentry, mode); + error = security_inode_create(&nop_mnt_idmap, dir, dentry, mode); if (error) return error; error = f(dentry, mode, arg); @@ -4328,7 +4328,7 @@ static int may_o_create(const struct mnt_idmap *idmap, if (error) return error; - return security_inode_create(dir->dentry->d_inode, dentry, mode); + return security_inode_create(idmap, dir->dentry->d_inode, dentry, mode); } /** @@ -5271,7 +5271,7 @@ int vfs_mknod(const struct mnt_idmap *idmap, struct inode *dir, if (error) return error; - error = security_inode_mknod(dir, dentry, mode, dev); + error = security_inode_mknod(idmap, dir, dentry, mode, dev); if (error) return error; @@ -5408,7 +5408,7 @@ struct dentry *vfs_mkdir(const struct mnt_idmap *idmap, struct inode *dir, goto err; mode = vfs_prepare_mode(idmap, dir, mode, S_IRWXUGO | S_ISVTX, S_IFDIR); - error = security_inode_mkdir(dir, dentry, mode); + error = security_inode_mkdir(idmap, dir, dentry, mode); if (error) goto err; @@ -5796,7 +5796,7 @@ int vfs_symlink(const struct mnt_idmap *idmap, struct inode *dir, if (!dir->i_op->symlink) return -EPERM; - error = security_inode_symlink(dir, dentry, oldname); + error = security_inode_symlink(idmap, dir, dentry, oldname); if (error) return error; @@ -5920,7 +5920,7 @@ int vfs_link(struct dentry *old_dentry, const struct mnt_idmap *idmap, if (S_ISDIR(inode->i_mode)) return -EPERM; - error = security_inode_link(old_dentry, dir, new_dentry); + error = security_inode_link(idmap, old_dentry, dir, new_dentry); if (error) return error; diff --git a/fs/namespace.c b/fs/namespace.c index 973efee4b968..d01b5402e847 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -4206,8 +4206,7 @@ static void dec_mnt_namespaces(struct ucounts *ucounts) static void free_mnt_ns(struct mnt_namespace *ns) { - if (!is_anon_ns(ns)) - ns_common_free(ns); + ns_common_free(ns); dec_mnt_namespaces(ns->ucounts); mnt_ns_tree_remove(ns); } diff --git a/include/linux/cred.h b/include/linux/cred.h index 6ef1750c93e2..49c26af37349 100644 --- a/include/linux/cred.h +++ b/include/linux/cred.h @@ -180,12 +180,18 @@ static inline bool cap_ambient_invariant_ok(const struct cred *cred) static inline const struct cred *override_creds(const struct cred *override_cred) { - return rcu_replace_pointer(current->cred, override_cred, 1); + const struct cred *old = current->cred; + + current->cred = override_cred; + return old; } static inline const struct cred *revert_creds(const struct cred *revert_cred) { - return rcu_replace_pointer(current->cred, revert_cred, 1); + const struct cred *override_cred = current->cred; + + current->cred = revert_cred; + return override_cred; } DEFINE_CLASS(override_creds, @@ -293,11 +299,10 @@ DEFINE_FREE(put_cred, struct cred *, if (!IS_ERR_OR_NULL(_T)) put_cred(_T)) /** * current_cred - Access the current task's subjective credentials * - * Access the subjective credentials of the current task. RCU-safe, - * since nobody else can modify it. + * Access the subjective credentials of the current task. + * Nobody else can modify it. */ -#define current_cred() \ - rcu_dereference_protected(current->cred, 1) +#define current_cred() (current->cred) /** * current_real_cred - Access the current task's objective credentials diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h index 584db296e43b..5cf0b4795065 100644 --- a/include/linux/lsm_audit.h +++ b/include/linux/lsm_audit.h @@ -44,7 +44,7 @@ struct lsm_network_audit { struct lsm_ioctlop_audit { struct path path; - u16 cmd; + unsigned int cmd; }; struct lsm_ibpkey_audit { @@ -78,6 +78,7 @@ struct common_audit_data { #define LSM_AUDIT_DATA_NOTIFICATION 16 #define LSM_AUDIT_DATA_ANONINODE 17 #define LSM_AUDIT_DATA_NLMSGTYPE 18 +#define LSM_AUDIT_DATA_NS 19 union { struct path path; struct dentry *dentry; @@ -100,6 +101,10 @@ struct common_audit_data { int reason; const char *anonclass; u16 nlmsg_type; + struct { + u32 ns_type; + u64 ns_id; + } ns; } u; /* this union contains LSM specific data */ union { diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index c9561564585e..ef16e5343e09 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -121,26 +121,27 @@ LSM_HOOK(int, -EOPNOTSUPP, inode_init_security, struct inode *inode, int *xattr_count) LSM_HOOK(int, 0, inode_init_security_anon, struct inode *inode, const struct qstr *name, const struct inode *context_inode) -LSM_HOOK(int, 0, inode_create, struct inode *dir, struct dentry *dentry, - umode_t mode) +LSM_HOOK(int, 0, inode_create, const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) LSM_HOOK(void, LSM_RET_VOID, inode_post_create_tmpfile, const struct mnt_idmap *idmap, struct inode *inode) -LSM_HOOK(int, 0, inode_link, struct dentry *old_dentry, struct inode *dir, - struct dentry *new_dentry) +LSM_HOOK(int, 0, inode_link, const struct mnt_idmap *idmap, + struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) LSM_HOOK(int, 0, inode_unlink, struct inode *dir, struct dentry *dentry) -LSM_HOOK(int, 0, inode_symlink, struct inode *dir, struct dentry *dentry, - const char *old_name) -LSM_HOOK(int, 0, inode_mkdir, struct inode *dir, struct dentry *dentry, - umode_t mode) +LSM_HOOK(int, 0, inode_symlink, const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name) +LSM_HOOK(int, 0, inode_mkdir, const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) LSM_HOOK(int, 0, inode_rmdir, struct inode *dir, struct dentry *dentry) -LSM_HOOK(int, 0, inode_mknod, struct inode *dir, struct dentry *dentry, - umode_t mode, dev_t dev) +LSM_HOOK(int, 0, inode_mknod, const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) LSM_HOOK(int, 0, inode_rename, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry) LSM_HOOK(int, 0, inode_readlink, struct dentry *dentry) LSM_HOOK(int, 0, inode_follow_link, struct dentry *dentry, struct inode *inode, bool rcu) -LSM_HOOK(int, 0, inode_permission, struct inode *inode, int mask) +LSM_HOOK(int, 0, inode_permission, const struct mnt_idmap *idmap, + struct inode *inode, int mask) LSM_HOOK(int, 0, inode_setattr, const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr) LSM_HOOK(void, LSM_RET_VOID, inode_post_setattr, const struct mnt_idmap *idmap, @@ -188,7 +189,7 @@ LSM_HOOK(int, 0, inode_setintegrity, const struct inode *inode, enum lsm_integrity_type type, const void *value, size_t size) LSM_HOOK(int, 0, kernfs_init_security, struct kernfs_node *kn_dir, struct kernfs_node *kn) -LSM_HOOK(int, 0, file_permission, struct file *file, int mask) +LSM_HOOK(int, 0, file_permission, const struct file *file, int mask) LSM_HOOK(int, 0, file_alloc_security, struct file *file) LSM_HOOK(void, LSM_RET_VOID, file_release, struct file *file) LSM_HOOK(void, LSM_RET_VOID, file_free_security, struct file *file) @@ -266,6 +267,9 @@ LSM_HOOK(int, -ENOSYS, task_prctl, int option, unsigned long arg2, LSM_HOOK(void, LSM_RET_VOID, task_to_inode, struct task_struct *p, struct inode *inode) LSM_HOOK(int, 0, userns_create, const struct cred *cred) +LSM_HOOK(int, 0, namespace_init, struct ns_common *ns) +LSM_HOOK(void, LSM_RET_VOID, namespace_free, struct ns_common *ns) +LSM_HOOK(int, 0, namespace_install, const struct nsset *nsset, struct ns_common *ns) LSM_HOOK(int, 0, ipc_permission, struct kern_ipc_perm *ipcp, short flag) LSM_HOOK(void, LSM_RET_VOID, ipc_getlsmprop, struct kern_ipc_perm *ipcp, struct lsm_prop *prop) diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index c4488c4a6d8a..13621e9e233e 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -112,6 +112,7 @@ struct lsm_blob_sizes { unsigned int lbs_ipc; unsigned int lbs_key; unsigned int lbs_msg_msg; + unsigned int lbs_ns; unsigned int lbs_perf_event; unsigned int lbs_task; unsigned int lbs_xattr_count; /* num xattr slots in new_xattrs array */ diff --git a/include/linux/ns/ns_common_types.h b/include/linux/ns/ns_common_types.h index 6ed6b497831c..fddc62be3b62 100644 --- a/include/linux/ns/ns_common_types.h +++ b/include/linux/ns/ns_common_types.h @@ -118,6 +118,9 @@ struct ns_common { unsigned int inum; struct ns_tree; struct rcu_head ns_rcu; +#ifdef CONFIG_SECURITY + void *ns_security; +#endif }; #define to_ns_common(__ns) \ diff --git a/include/linux/sched.h b/include/linux/sched.h index 87ed6705c427..64b7581e2012 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -1172,8 +1172,12 @@ struct task_struct { /* Objective and real subjective task credentials (COW): */ const struct cred __rcu *real_cred; - /* Effective (overridable) subjective task credentials (COW): */ - const struct cred __rcu *cred; + /* + * Effective (overridable) subjective task credentials (COW). + * Only accessible for the current task and during task creation/freeing. + * This pointer is not managed by RCU! + */ + const struct cred *cred; #ifdef CONFIG_KEYS /* Cached requested key. */ diff --git a/include/linux/security.h b/include/linux/security.h index f7ff72ff956b..8b8d6b8b802f 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -67,6 +67,7 @@ enum fs_value_type; struct watch; struct watch_notification; struct lsm_ctx; +struct nsset; /* Default (no) options for the capable function */ #define CAP_OPT_NONE 0x0 @@ -80,6 +81,7 @@ struct lsm_ctx; struct ctl_table; struct audit_krule; +struct ns_common; struct user_namespace; struct timezone; @@ -405,24 +407,28 @@ int security_inode_init_security(struct inode *inode, struct inode *dir, int security_inode_init_security_anon(struct inode *inode, const struct qstr *name, const struct inode *context_inode); -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); void security_inode_post_create_tmpfile(const struct mnt_idmap *idmap, struct inode *inode); -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); int security_inode_unlink(struct inode *dir, struct dentry *dentry); -int security_inode_symlink(struct inode *dir, struct dentry *dentry, - const char *old_name); -int security_inode_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode); +int security_inode_symlink(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name); +int security_inode_mkdir(const struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode); int security_inode_rmdir(struct inode *dir, struct dentry *dentry); -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); int security_inode_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags); int security_inode_readlink(struct dentry *dentry); int security_inode_follow_link(struct dentry *dentry, struct inode *inode, bool rcu); -int security_inode_permission(struct inode *inode, int mask); +int security_inode_permission(const struct mnt_idmap *idmap, struct inode *inode, + int mask); int security_inode_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); void security_inode_post_setattr(const struct mnt_idmap *idmap, struct dentry *dentry, @@ -469,7 +475,7 @@ int security_inode_setintegrity(const struct inode *inode, size_t size); int security_kernfs_init_security(struct kernfs_node *kn_dir, struct kernfs_node *kn); -int security_file_permission(struct file *file, int mask); +int security_file_permission(const struct file *file, int mask); int security_file_alloc(struct file *file); void security_file_release(struct file *file); void security_file_free(struct file *file); @@ -541,6 +547,9 @@ int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5); void security_task_to_inode(struct task_struct *p, struct inode *inode); int security_create_user_ns(const struct cred *cred); +int security_namespace_init(struct ns_common *ns); +void security_namespace_free(struct ns_common *ns); +int security_namespace_install(const struct nsset *nsset, struct ns_common *ns); int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag); void security_ipc_getlsmprop(struct kern_ipc_perm *ipcp, struct lsm_prop *prop); int security_msg_msg_alloc(struct msg_msg *msg); @@ -909,9 +918,10 @@ static inline int security_inode_init_security_anon(struct inode *inode, return 0; } -static inline int security_inode_create(struct inode *dir, - struct dentry *dentry, - umode_t mode) +static inline int security_inode_create(const struct mnt_idmap *idmap, + struct inode *dir, + struct dentry *dentry, + umode_t mode) { return 0; } @@ -920,9 +930,10 @@ static inline void security_inode_post_create_tmpfile(const struct mnt_idmap *idmap, struct inode *inode) { } -static inline int security_inode_link(struct dentry *old_dentry, - struct inode *dir, - struct dentry *new_dentry) +static inline int security_inode_link(const struct mnt_idmap *idmap, + struct dentry *old_dentry, + struct inode *dir, + struct dentry *new_dentry) { return 0; } @@ -933,16 +944,18 @@ static inline int security_inode_unlink(struct inode *dir, return 0; } -static inline int security_inode_symlink(struct inode *dir, - struct dentry *dentry, - const char *old_name) +static inline int security_inode_symlink(const struct mnt_idmap *idmap, + struct inode *dir, + struct dentry *dentry, + const char *old_name) { return 0; } -static inline int security_inode_mkdir(struct inode *dir, - struct dentry *dentry, - int mode) +static inline int security_inode_mkdir(const struct mnt_idmap *idmap, + struct inode *dir, + struct dentry *dentry, + int mode) { return 0; } @@ -953,9 +966,10 @@ static inline int security_inode_rmdir(struct inode *dir, return 0; } -static inline int security_inode_mknod(struct inode *dir, - struct dentry *dentry, - int mode, dev_t dev) +static inline int security_inode_mknod(const struct mnt_idmap *idmap, + struct inode *dir, + struct dentry *dentry, + int mode, dev_t dev) { return 0; } @@ -981,7 +995,8 @@ static inline int security_inode_follow_link(struct dentry *dentry, return 0; } -static inline int security_inode_permission(struct inode *inode, int mask) +static inline int security_inode_permission(const struct mnt_idmap *idmap, + struct inode *inode, int mask) { return 0; } @@ -1139,7 +1154,7 @@ static inline int security_inode_copy_up_xattr(struct dentry *src, const char *n return -EOPNOTSUPP; } -static inline int security_file_permission(struct file *file, int mask) +static inline int security_file_permission(const struct file *file, int mask) { return 0; } @@ -1438,6 +1453,21 @@ static inline int security_create_user_ns(const struct cred *cred) return 0; } +static inline int security_namespace_init(struct ns_common *ns) +{ + return 0; +} + +static inline void security_namespace_free(struct ns_common *ns) +{ +} + +static inline int security_namespace_install(const struct nsset *nsset, + struct ns_common *ns) +{ + return 0; +} + static inline int security_ipc_permission(struct kern_ipc_perm *ipcp, short flag) { diff --git a/include/uapi/linux/nsfs.h b/include/uapi/linux/nsfs.h index 007fed5971b4..e5909266ec3f 100644 --- a/include/uapi/linux/nsfs.h +++ b/include/uapi/linux/nsfs.h @@ -55,6 +55,7 @@ enum init_ns_ino { MNT_NS_INIT_INO = 0xEFFFFFF8U, #ifdef __KERNEL__ MNT_NS_ANON_INO = 0xEFFFFFF7U, + MNT_NS_INO_SPECIAL_MAX = MNT_NS_ANON_INO, #endif }; diff --git a/init/init_task.c b/init/init_task.c index adb207cd987c..ce7c2b07d855 100644 --- a/init/init_task.c +++ b/init/init_task.c @@ -160,7 +160,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { .sibling = LIST_HEAD_INIT(init_task.sibling), .group_leader = &init_task, RCU_POINTER_INITIALIZER(real_cred, &init_cred), - RCU_POINTER_INITIALIZER(cred, &init_cred), + .cred = &init_cred, .comm = INIT_TASK_COMM, .thread = INIT_THREAD, .real_fs = &init_fs, diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 2b9ce0b52511..1b9cf25291e0 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -459,7 +459,7 @@ static int audit_field_compare(struct task_struct *tsk, * * If task_creation is true, this is an explicit indication that we are * filtering a task rule at task creation time. This and tsk == current are - * the only situations where tsk->cred may be accessed without an rcu read lock. + * the only situations where tsk->cred may be accessed. */ static int audit_filter_rules(struct task_struct *tsk, struct audit_krule *rule, @@ -476,7 +476,8 @@ static int audit_filter_rules(struct task_struct *tsk, if (ctx && rule->prio <= ctx->prio) return 0; - cred = rcu_dereference_check(tsk->cred, tsk == current || task_creation); + WARN_ON(tsk != current && !task_creation); + cred = tsk->cred; for (i = 0; i < rule->field_count; i++) { struct audit_field *f = &rule->fields[i]; diff --git a/kernel/cred.c b/kernel/cred.c index 3df4e15bd67f..d01df40a9a38 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -414,7 +414,7 @@ int commit_creds(struct cred *new) inc_rlimit_ucounts(new->ucounts, UCOUNT_RLIMIT_NPROC, 1); rcu_assign_pointer(task->real_cred, new); - rcu_assign_pointer(task->cred, new); + task->cred = new; if (new->user != old->user || new->user_ns != old->user_ns) dec_rlimit_ucounts(old->ucounts, UCOUNT_RLIMIT_NPROC, 1); if (new->user_ns != old->user_ns) @@ -537,7 +537,8 @@ void __init cred_init(void) { /* allocate a slab in which we can store credentials */ cred_jar = KMEM_CACHE(cred, - SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT); + SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT | + SLAB_NO_MERGE); } /** diff --git a/kernel/nscommon.c b/kernel/nscommon.c index 3166c1fd844a..e72426bba29a 100644 --- a/kernel/nscommon.c +++ b/kernel/nscommon.c @@ -4,6 +4,7 @@ #include <linux/ns_common.h> #include <linux/nstree.h> #include <linux/proc_ns.h> +#include <linux/security.h> #include <linux/user_namespace.h> #include <linux/vfsdebug.h> @@ -59,6 +60,9 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope refcount_set(&ns->__ns_ref, 1); ns->stashed = NULL; +#ifdef CONFIG_SECURITY + ns->ns_security = NULL; +#endif ns->ops = ops; ns->ns_id = 0; ns->ns_type = ns_type; @@ -77,6 +81,14 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope ret = proc_alloc_inum(&ns->inum); if (ret) return ret; + + ret = security_namespace_init(ns); + if (ret) { + if (!inum) + proc_free_inum(ns->inum); + return ret; + } + /* * Tree ref starts at 0. It's incremented when namespace enters * active use (installed in nsproxy) and decremented when all @@ -91,7 +103,10 @@ int __ns_common_init(struct ns_common *ns, u32 ns_type, const struct proc_ns_ope void __ns_common_free(struct ns_common *ns) { - proc_free_inum(ns->inum); + security_namespace_free(ns); + + if (ns->inum > MNT_NS_INO_SPECIAL_MAX) + proc_free_inum(ns->inum); } struct ns_common *__must_check ns_owner(struct ns_common *ns) diff --git a/kernel/nsproxy.c b/kernel/nsproxy.c index d9d3d5973bf5..0f1b208d8eef 100644 --- a/kernel/nsproxy.c +++ b/kernel/nsproxy.c @@ -385,6 +385,12 @@ out: static inline int validate_ns(struct nsset *nsset, struct ns_common *ns) { + int ret; + + ret = security_namespace_install(nsset, ns); + if (ret) + return ret; + return ns->ops->install(nsset, ns); } diff --git a/rust/kernel/security.rs b/rust/kernel/security.rs index 9d271695265f..4dc3eba6ce84 100644 --- a/rust/kernel/security.rs +++ b/rust/kernel/security.rs @@ -62,8 +62,7 @@ impl SecurityCtx { /// Get the security context given its id. #[inline] pub fn from_secid(secid: u32) -> Result<Self> { - // SAFETY: `struct lsm_context` can be initialized to all zeros. - let mut ctx: bindings::lsm_context = unsafe { core::mem::zeroed() }; + let mut ctx: bindings::lsm_context = pin_init::zeroed(); // SAFETY: Just a C FFI call. The pointer is valid for writes. to_result(unsafe { bindings::security_secid_to_secctx(secid, &mut ctx) })?; diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index b908e744818c..de80561348fc 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -715,7 +715,7 @@ static void update_peer_ctx(struct sock *sk, struct aa_sk_ctx *ctx, * boundaries. Otherwise cached info off file is sufficient */ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, - const char *op, u32 request, struct file *file) + const char *op, u32 request, const struct file *file) { struct socket *sock = (struct socket *) file->private_data; struct sockaddr_un *addr, *peer_addr; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 3e74b613db32..305fdb341ddb 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -489,7 +489,7 @@ static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label, static int __file_path_perm(const char *op, const struct cred *subj_cred, struct aa_label *label, - struct aa_label *flabel, struct file *file, + struct aa_label *flabel, const struct file *file, u32 request, u32 denied, bool in_atomic) { struct aa_profile *profile; @@ -550,7 +550,7 @@ static int __file_path_perm(const char *op, const struct cred *subj_cred, static int __file_sock_perm(const char *op, const struct cred *subj_cred, struct aa_label *label, - struct aa_label *flabel, struct file *file, + struct aa_label *flabel, const struct file *file, u32 request, u32 denied) { int error; @@ -579,7 +579,7 @@ static bool __file_is_delegated(struct aa_label *obj_label) return unconfined(obj_label); } -static bool __is_unix_file(struct file *file) +static bool __is_unix_file(const struct file *file) { struct socket *sock = (struct socket *) file->private_data; @@ -595,7 +595,7 @@ static bool __is_unix_file(struct file *file) return false; } -static bool __unix_needs_revalidation(struct file *file, struct aa_label *label, +static bool __unix_needs_revalidation(const struct file *file, struct aa_label *label, u32 request) { struct socket *sock = (struct socket *) file->private_data; @@ -624,7 +624,7 @@ static bool __unix_needs_revalidation(struct file *file, struct aa_label *label, * Returns: %0 if access allowed else error */ int aa_file_perm(const char *op, const struct cred *subj_cred, - struct aa_label *label, struct file *file, + struct aa_label *label, const struct file *file, u32 request, bool in_atomic) { struct aa_file_ctx *fctx; diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h index 4a62e600d82b..62e3269273eb 100644 --- a/security/apparmor/include/af_unix.h +++ b/security/apparmor/include/af_unix.h @@ -50,6 +50,6 @@ int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, int optname); int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, - const char *op, u32 request, struct file *file); + const char *op, u32 request, const struct file *file); #endif /* __AA_AF_UNIX_H */ diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 1614c07fc53e..d69a0611a981 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -29,7 +29,7 @@ struct path; AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_LOCK | \ AA_EXEC_MMAP | AA_MAY_LINK) -static inline struct aa_file_ctx *file_ctx(struct file *file) +static inline struct aa_file_ctx *file_ctx(const struct file *file) { return file->f_security + apparmor_blob_sizes.lbs_file; } @@ -97,7 +97,7 @@ int aa_path_link(const struct cred *subj_cred, struct aa_label *label, struct dentry *new_dentry); int aa_file_perm(const char *op, const struct cred *subj_cred, - struct aa_label *label, struct file *file, + struct aa_label *label, const struct file *file, u32 request, bool in_atomic); void aa_inherit_files(const struct cred *cred, struct files_struct *files); diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index 375341929cb6..e387b50920b3 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -112,7 +112,7 @@ int aa_label_sk_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, const struct sock *sk); int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, - struct file *file); + const struct file *file); int apparmor_secmark_check(struct aa_label *label, char *op, u32 request, u32 secid, const struct sock *sk); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index c73681d820a0..9d572cf8486b 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -525,7 +525,7 @@ static void apparmor_file_free_security(struct file *file) aa_put_label(rcu_access_pointer(ctx->label)); } -static int common_file_perm(const char *op, struct file *file, u32 mask) +static int common_file_perm(const char *op, const struct file *file, u32 mask) { struct aa_label *label; bool needput; @@ -543,7 +543,7 @@ static int apparmor_file_receive(struct file *file) return common_file_perm(OP_FRECEIVE, file, aa_map_file_to_perms(file)); } -static int apparmor_file_permission(struct file *file, int mask) +static int apparmor_file_permission(const struct file *file, int mask) { return common_file_perm(OP_FPERM, file, mask); } diff --git a/security/apparmor/net.c b/security/apparmor/net.c index a333e6aff926..6048e3c5533d 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -325,7 +325,7 @@ int aa_sk_perm(const char *op, u32 request, const struct sock *sk) int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, - const char *op, u32 request, struct file *file) + const char *op, u32 request, const struct file *file) { struct socket *sock = (struct socket *) file->private_data; 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 f476b0736c9a..da8d48cd0a2a 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) @@ -1639,6 +1653,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 @@ -1647,12 +1662,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); @@ -1673,6 +1688,7 @@ void security_inode_post_create_tmpfile(const 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 @@ -1681,12 +1697,12 @@ void security_inode_post_create_tmpfile(const 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); } /** @@ -1707,6 +1723,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 @@ -1715,16 +1732,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 @@ -1734,11 +1752,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); @@ -1760,6 +1779,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 @@ -1772,12 +1792,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); } /** @@ -1848,6 +1868,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 * @@ -1860,11 +1881,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); } /** @@ -2412,7 +2434,7 @@ int security_kernfs_init_security(struct kernfs_node *kn_dir, * * Return: Returns 0 if permission is granted. */ -int security_file_permission(struct file *file, int mask) +int security_file_permission(const struct file *file, int mask) { return call_int_hook(file_permission, file, mask); } @@ -2511,9 +2533,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); } @@ -3383,6 +3404,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/hooks.c b/security/selinux/hooks.c index 73496690ab52..88f3a5858c2a 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3110,12 +3110,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); } @@ -3125,12 +3127,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); } @@ -3140,7 +3144,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)); } @@ -3272,13 +3277,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; @@ -3835,7 +3842,7 @@ static int selinux_kernfs_init_security(struct kernfs_node *kn_dir, /* file security operations */ -static int selinux_revalidate_file_permission(struct file *file, int mask) +static int selinux_revalidate_file_permission(const struct file *file, int mask) { const struct cred *cred = current_cred(); struct inode *inode = file_inode(file); @@ -3848,7 +3855,7 @@ static int selinux_revalidate_file_permission(struct file *file, int mask) file_mask_to_av(inode->i_mode, mask)); } -static int selinux_file_permission(struct file *file, int mask) +static int selinux_file_permission(const struct file *file, int mask) { struct inode *inode = file_inode(file); struct file_security_struct *fsec = selinux_file(file); @@ -3952,7 +3959,7 @@ static void selinux_backing_file_free(struct file *backing_file) * 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); @@ -4023,14 +4030,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; } diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index bb78569b3d5b..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; diff --git a/tools/testing/selftests/bpf/prog_tests/test_lsm.c b/tools/testing/selftests/bpf/prog_tests/test_lsm.c index d7495efd4a56..c0ae813698ae 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_lsm.c +++ b/tools/testing/selftests/bpf/prog_tests/test_lsm.c @@ -1,18 +1,30 @@ // SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE /* * Copyright (C) 2020 Google LLC. */ #include <test_progs.h> +#include <sched.h> +#include <signal.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> +#include <linux/mount.h> + #include "lsm.skel.h" #include "lsm_tailcall.skel.h" char *CMD_ARGS[] = {"true", NULL}; +enum { + INODE_IDMAP_ALL = (1U << 6) - 1, +}; + int exec_cmd(int *monitored_pid) { int child_pid, child_status; @@ -30,6 +42,219 @@ int exec_cmd(int *monitored_pid) return -EINVAL; } +static ssize_t write_nointr(int fd, const void *buf, size_t count) +{ + ssize_t ret; + + do { + ret = write(fd, buf, count); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static int write_file(const char *path, const char *value) +{ + size_t len = strlen(value); + int fd, saved_errno = 0; + ssize_t ret; + + fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); + if (fd < 0) + return -1; + + ret = write_nointr(fd, value, len); + if (ret < 0) + saved_errno = errno; + else if ((size_t)ret != len) + saved_errno = EIO; + close(fd); + if (saved_errno) { + errno = saved_errno; + return -1; + } + return 0; +} + +static int write_userns_file(pid_t pid, const char *name, const char *value) +{ + char path[64]; + int len; + + len = snprintf(path, sizeof(path), "/proc/%d/%s", pid, name); + if (len < 0 || (size_t)len >= sizeof(path)) { + errno = EOVERFLOW; + return -1; + } + + return write_file(path, value); +} + +static int create_userns_fd(void) +{ + char path[64]; + pid_t pid, waited; + int fd = -1, len, saved_errno, status; + + pid = fork(); + if (pid < 0) + return -1; + if (pid == 0) { + if (unshare(CLONE_NEWUSER)) + _exit(1); + raise(SIGSTOP); + _exit(0); + } + + do { + waited = waitpid(pid, &status, WUNTRACED); + } while (waited < 0 && errno == EINTR); + if (waited != pid) + goto out; + if (!WIFSTOPPED(status)) { + pid = -1; + goto out; + } + + /* A one-entry map is identity for root but remains distinct from nop_mnt_idmap. */ + if (write_userns_file(pid, "setgroups", "deny") && errno != ENOENT) + goto out; + if (write_userns_file(pid, "uid_map", "0 0 1") || + write_userns_file(pid, "gid_map", "0 0 1")) + goto out; + + len = snprintf(path, sizeof(path), "/proc/%d/ns/user", pid); + if (len < 0 || (size_t)len >= sizeof(path)) { + errno = EOVERFLOW; + goto out; + } + fd = open(path, O_RDONLY | O_CLOEXEC); + +out: + saved_errno = errno; + if (pid > 0) { + kill(pid, SIGKILL); + do { + waited = waitpid(pid, NULL, 0); + } while (waited < 0 && errno == EINTR); + } + errno = saved_errno; + return fd; +} + +static int create_idmapped_tmpfs(void) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int fsfd = -1, mntfd = -1, saved_errno, userns_fd = -1; + + userns_fd = create_userns_fd(); + if (userns_fd < 0) + goto out; + + /* A detached tmpfs avoids relying on the host test directory supporting idmaps. */ + fsfd = syscall(__NR_fsopen, "tmpfs", FSOPEN_CLOEXEC); + if (fsfd < 0) + goto out; + if (syscall(__NR_fsconfig, fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0)) + goto out; + + mntfd = syscall(__NR_fsmount, fsfd, FSMOUNT_CLOEXEC, 0); + if (mntfd < 0) + goto out; + + attr.userns_fd = userns_fd; + if (syscall(__NR_mount_setattr, mntfd, "", AT_EMPTY_PATH, &attr, + sizeof(attr))) { + close(mntfd); + mntfd = -1; + } + +out: + saved_errno = errno; + if (fsfd >= 0) + close(fsfd); + if (userns_fd >= 0) + close(userns_fd); + errno = saved_errno; + return mntfd; +} + +static int exercise_inode_idmap_hooks(int dirfd) +{ + int fd = -1, ret = -1; + + fd = openat(dirfd, "file", O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, + 0600); + if (!ASSERT_GE(fd, 0, "create")) + goto out; + close(fd); + fd = -1; + + if (!ASSERT_OK(mkdirat(dirfd, "dir", 0700), "mkdir")) + goto out; + if (!ASSERT_OK(symlinkat("target", dirfd, "symlink"), "symlink")) + goto out; + if (!ASSERT_OK(linkat(dirfd, "file", dirfd, "link", 0), "link")) + goto out; + if (!ASSERT_OK(mkfifoat(dirfd, "fifo", 0600), "mknod")) + goto out; + + ret = 0; +out: + if (fd >= 0) + close(fd); + unlinkat(dirfd, "link", 0); + unlinkat(dirfd, "fifo", 0); + unlinkat(dirfd, "symlink", 0); + unlinkat(dirfd, "file", 0); + unlinkat(dirfd, "dir", AT_REMOVEDIR); + return ret; +} + +static int test_lsm_inode_idmap(struct lsm *skel) +{ + char tmpdir[] = "/var/tmp/test_lsm_idmap.XXXXXX"; + __u32 expected = INODE_IDMAP_ALL; + int dirfd = -1, idmapped_dirfd = -1; + int ret = -1; + + if (!ASSERT_OK_PTR(mkdtemp(tmpdir), "mkdtemp")) + return -1; + + dirfd = open(tmpdir, O_RDONLY | O_DIRECTORY | O_CLOEXEC); + if (!ASSERT_GE(dirfd, 0, "open_tmpdir")) + goto out; + + idmapped_dirfd = create_idmapped_tmpfs(); + if (!ASSERT_GE(idmapped_dirfd, 0, "create_idmapped_tmpfs")) + goto out; + + skel->bss->inode_identity_idmap_seen = 0; + skel->bss->inode_idmapped_mount_seen = 0; + + if (!ASSERT_OK(exercise_inode_idmap_hooks(dirfd), "identity_idmap")) + goto out; + if (!ASSERT_OK(exercise_inode_idmap_hooks(idmapped_dirfd), + "idmapped_mount")) + goto out; + + if (!ASSERT_EQ(skel->bss->inode_identity_idmap_seen, expected, + "inode_identity_idmap_seen")) + goto out; + ret = ASSERT_EQ(skel->bss->inode_idmapped_mount_seen, expected, + "inode_idmapped_mount_seen") ? 0 : -1; + +out: + if (idmapped_dirfd >= 0) + close(idmapped_dirfd); + if (dirfd >= 0) + close(dirfd); + rmdir(tmpdir); + return ret; +} + static int test_lsm(struct lsm *skel) { struct bpf_link *link; @@ -53,6 +278,10 @@ static int test_lsm(struct lsm *skel) skel->bss->monitored_pid = getpid(); + err = test_lsm_inode_idmap(skel); + if (!ASSERT_OK(err, "test_lsm_inode_idmap")) + return err; + err = stack_mprotect(); if (!ASSERT_EQ(err, -1, "stack_mprotect") || !ASSERT_EQ(errno, EPERM, "stack_mprotect")) @@ -71,6 +300,8 @@ static int test_lsm(struct lsm *skel) skel->bss->copy_test = 0; skel->bss->bprm_count = 0; skel->bss->mprotect_count = 0; + skel->bss->inode_identity_idmap_seen = 0; + skel->bss->inode_idmapped_mount_seen = 0; return 0; } diff --git a/tools/testing/selftests/bpf/progs/lsm.c b/tools/testing/selftests/bpf/progs/lsm.c index 7441d66c080c..e210e07d3a37 100644 --- a/tools/testing/selftests/bpf/progs/lsm.c +++ b/tools/testing/selftests/bpf/progs/lsm.c @@ -84,6 +84,85 @@ char _license[] SEC("license") = "GPL"; int monitored_pid = 0; int mprotect_count = 0; int bprm_count = 0; +__u32 inode_identity_idmap_seen = 0; +__u32 inode_idmapped_mount_seen = 0; + +enum { + INODE_IDMAP_CREATE = 1U << 0, + INODE_IDMAP_LINK = 1U << 1, + INODE_IDMAP_SYMLINK = 1U << 2, + INODE_IDMAP_MKDIR = 1U << 3, + INODE_IDMAP_MKNOD = 1U << 4, + INODE_IDMAP_PERMISSION = 1U << 5, +}; + +static __always_inline bool is_monitored_idmap(struct mnt_idmap *idmap) +{ + __u32 pid = bpf_get_current_pid_tgid() >> 32; + + return monitored_pid == pid && idmap; +} + +static __always_inline bool is_identity_idmap(struct mnt_idmap *idmap) +{ + return idmap->uid_map.nr_extents == 0 && + idmap->gid_map.nr_extents == 0; +} + +static __always_inline int record_inode_idmap(struct mnt_idmap *idmap, + __u32 hook, int ret) +{ + if (ret || !is_monitored_idmap(idmap)) + return ret; + if (is_identity_idmap(idmap)) + inode_identity_idmap_seen |= hook; + else + inode_idmapped_mount_seen |= hook; + return 0; +} + +SEC("lsm/inode_create") +int BPF_PROG(test_inode_create, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_CREATE, ret); +} + +SEC("lsm/inode_link") +int BPF_PROG(test_inode_link, struct mnt_idmap *idmap, + struct dentry *old_dentry, struct inode *dir, + struct dentry *new_dentry, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_LINK, ret); +} + +SEC("lsm/inode_symlink") +int BPF_PROG(test_inode_symlink, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, const char *old_name, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_SYMLINK, ret); +} + +SEC("lsm/inode_mkdir") +int BPF_PROG(test_inode_mkdir, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_MKDIR, ret); +} + +SEC("lsm/inode_mknod") +int BPF_PROG(test_inode_mknod, struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_MKNOD, ret); +} + +SEC("lsm/inode_permission") +int BPF_PROG(test_inode_permission, struct mnt_idmap *idmap, + struct inode *inode, int mask, int ret) +{ + return record_inode_idmap(idmap, INODE_IDMAP_PERMISSION, ret); +} SEC("lsm/file_mprotect") int BPF_PROG(test_int_hook, struct vm_area_struct *vma, diff --git a/tools/testing/selftests/landlock/fs_test.c b/tools/testing/selftests/landlock/fs_test.c index 6e979cef884d..fd20a2b3c0a5 100644 --- a/tools/testing/selftests/landlock/fs_test.c +++ b/tools/testing/selftests/landlock/fs_test.c @@ -4146,6 +4146,9 @@ TEST_F_FORK(layout1, o_path_ftruncate_and_ioctl) ASSERT_EQ(0, close(fd)); } +/* Arbitrary command with nonzero bits in both 16-bit halves. */ +static const unsigned int unknown_ioctl_cmd = 0xc00ffeee; + /* * ioctl_error - generically call the given ioctl with a pointer to a * sufficiently large zeroed-out memory region. @@ -4249,7 +4252,7 @@ TEST_F_FORK(layout1, blanket_permitted_ioctls) EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FS_IOC_ZERO_RANGE)); /* Default case is also blocked. */ - EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, 0xc00ffeee)); + EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, unknown_ioctl_cmd)); ASSERT_EQ(0, close(fd)); } @@ -7943,6 +7946,7 @@ TEST_F(audit_layout1, truncate) EXPECT_EQ(1, records.domain); } +/* Checks that audit records preserve every ioctl command bit. */ TEST_F(audit_layout1, ioctl_dev) { struct audit_records records; @@ -7952,10 +7956,10 @@ TEST_F(audit_layout1, ioctl_dev) fd = open("/dev/null", O_RDONLY | O_CLOEXEC); ASSERT_LE(0, fd); - EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, FIONREAD)); + EXPECT_EQ(EACCES, ioctl_error(_metadata, fd, unknown_ioctl_cmd)); EXPECT_EQ(0, matches_log_fs_extra(_metadata, self->audit_fd, "fs\\.ioctl_dev", "/dev/null", - " ioctlcmd=0x541b")); + " ioctlcmd=0xc00ffeee")); EXPECT_EQ(0, audit_count_records(self->audit_fd, &records)); EXPECT_EQ(0, records.access); |
