#include <linux/string.h>
#include <linux/msg.h>
#include <net/flow.h>
+#include <net/sock.h>
#define MAX_LSM_EVM_XATTR 2
[LOCKDOWN_MMIOTRACE] = "unsafe mmio",
[LOCKDOWN_DEBUGFS] = "debugfs access",
[LOCKDOWN_XMON_WR] = "xmon write access",
+ [LOCKDOWN_BPF_WRITE_USER] = "use of bpf to write user RAM",
[LOCKDOWN_INTEGRITY_MAX] = "integrity",
[LOCKDOWN_KCORE] = "/proc/kcore access",
[LOCKDOWN_KPROBES] = "use of kprobes",
- [LOCKDOWN_BPF_READ] = "use of bpf to read kernel RAM",
+ [LOCKDOWN_BPF_READ_KERNEL] = "use of bpf to read kernel RAM",
[LOCKDOWN_PERF] = "unsafe use of perf",
[LOCKDOWN_TRACEFS] = "use of tracefs",
[LOCKDOWN_XMON_RW] = "xmon read and write access",
static struct kmem_cache *lsm_inode_cache;
char *lsm_names;
-static struct lsm_blob_sizes blob_sizes __lsm_ro_after_init;
+
+/*
+ * The task blob includes the "display" slot used for
+ * chosing which module presents contexts.
+ */
+static struct lsm_blob_sizes blob_sizes __lsm_ro_after_init = {
+ .lbs_task = sizeof(int),
+};
/* Boot-time LSM user choice */
static __initdata const char *chosen_lsm_order;
lsm_set_blob_size(&needed->lbs_ipc, &blob_sizes.lbs_ipc);
lsm_set_blob_size(&needed->lbs_msg_msg, &blob_sizes.lbs_msg_msg);
lsm_set_blob_size(&needed->lbs_superblock, &blob_sizes.lbs_superblock);
+ lsm_set_blob_size(&needed->lbs_sock, &blob_sizes.lbs_sock);
lsm_set_blob_size(&needed->lbs_task, &blob_sizes.lbs_task);
}
init_debug("ipc blob size = %d\n", blob_sizes.lbs_ipc);
init_debug("msg_msg blob size = %d\n", blob_sizes.lbs_msg_msg);
init_debug("superblock blob size = %d\n", blob_sizes.lbs_superblock);
+ init_debug("sock blob size = %d\n", blob_sizes.lbs_sock);
init_debug("task blob size = %d\n", blob_sizes.lbs_task);
+ init_debug("lsmblob size = %zu\n", sizeof(struct lsmblob));
/*
* Create any kmem_caches needed for blobs
return 0;
}
+/*
+ * Current index to use while initializing the lsmblob secid list.
+ * Pointers to the LSM id structures for local use.
+ */
+static int lsm_slot __lsm_ro_after_init;
+static struct lsm_id *lsm_slotlist[LSMBLOB_ENTRIES];
+
/**
* security_add_hooks - Add a modules hooks to the hook lists.
* @hooks: the hooks to add
* @count: the number of hooks to add
- * @lsm: the name of the security module
+ * @lsmid: the the identification information for the security module
*
* Each LSM has to register its hooks with the infrastructure.
+ * If the LSM is using hooks that export secids allocate a slot
+ * for it in the lsmblob.
*/
void __init security_add_hooks(struct security_hook_list *hooks, int count,
- char *lsm)
+ struct lsm_id *lsmid)
{
int i;
+ if (lsmid->slot == LSMBLOB_NEEDED) {
+ if (lsm_slot >= LSMBLOB_ENTRIES)
+ panic("%s Too many LSMs registered.\n", __func__);
+ lsm_slotlist[lsm_slot] = lsmid;
+ lsmid->slot = lsm_slot++;
+ init_debug("%s assigned lsmblob slot %d\n", lsmid->lsm,
+ lsmid->slot);
+ }
+
for (i = 0; i < count; i++) {
- hooks[i].lsm = lsm;
+ hooks[i].lsmid = lsmid;
hlist_add_tail_rcu(&hooks[i].list, hooks[i].head);
}
* and fix this up afterwards.
*/
if (slab_is_available()) {
- if (lsm_append(lsm, &lsm_names) < 0)
+ if (lsm_append(lsmid->lsm, &lsm_names) < 0)
panic("%s - Cannot get early memory.\n", __func__);
}
}
*/
static int lsm_task_alloc(struct task_struct *task)
{
+ int *display;
+
if (blob_sizes.lbs_task == 0) {
task->security = NULL;
return 0;
task->security = kzalloc(blob_sizes.lbs_task, GFP_KERNEL);
if (task->security == NULL)
return -ENOMEM;
+
+ /*
+ * The start of the task blob contains the "display" LSM slot number.
+ * Start with it set to the invalid slot number, indicating that the
+ * default first registered LSM be displayed.
+ */
+ display = task->security;
+ *display = LSMBLOB_INVALID;
+
return 0;
}
return 0;
}
+/**
+ * lsm_sock_alloc - allocate a composite sock blob
+ * @sock: the sock that needs a blob
+ * @priority: allocation mode
+ *
+ * Allocate the sock blob for all the modules
+ *
+ * Returns 0, or -ENOMEM if memory can't be allocated.
+ */
+static int lsm_sock_alloc(struct sock *sock, gfp_t priority)
+{
+ if (blob_sizes.lbs_sock == 0) {
+ sock->sk_security = NULL;
+ return 0;
+ }
+
+ sock->sk_security = kzalloc(blob_sizes.lbs_sock, priority);
+ if (sock->sk_security == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
/**
* lsm_early_task - during initialization allocate a composite task blob
* @task: the task that needs a blob
{
return call_int_hook(binder_set_context_mgr, 0, mgr);
}
+EXPORT_SYMBOL(security_binder_set_context_mgr);
int security_binder_transaction(struct task_struct *from,
struct task_struct *to)
{
return call_int_hook(binder_transaction, 0, from, to);
}
+EXPORT_SYMBOL(security_binder_transaction);
int security_binder_transfer_binder(struct task_struct *from,
struct task_struct *to)
{
return call_int_hook(binder_transfer_binder, 0, from, to);
}
+EXPORT_SYMBOL(security_binder_transfer_binder);
int security_binder_transfer_file(struct task_struct *from,
struct task_struct *to, struct file *file)
{
return call_int_hook(binder_transfer_file, 0, from, to, file);
}
+EXPORT_SYMBOL(security_binder_transfer_file);
int security_ptrace_access_check(struct task_struct *child, unsigned int mode)
{
}
EXPORT_SYMBOL(security_inode_listsecurity);
-void security_inode_getsecid(struct inode *inode, u32 *secid)
+void security_inode_getsecid(struct inode *inode, struct lsmblob *blob)
{
- call_void_hook(inode_getsecid, inode, secid);
+ struct security_hook_list *hp;
+
+ lsmblob_init(blob, 0);
+ hlist_for_each_entry(hp, &security_hook_heads.inode_getsecid, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ hp->hook.inode_getsecid(inode, &blob->secid[hp->lsmid->slot]);
+ }
}
int security_inode_copy_up(struct dentry *src, struct cred **new)
int security_task_alloc(struct task_struct *task, unsigned long clone_flags)
{
+ int *odisplay = current->security;
+ int *ndisplay;
int rc = lsm_task_alloc(task);
- if (rc)
+ if (unlikely(rc))
return rc;
+
rc = call_int_hook(task_alloc, 0, task, clone_flags);
- if (unlikely(rc))
+ if (unlikely(rc)) {
security_task_free(task);
- return rc;
+ return rc;
+ }
+
+ if (odisplay) {
+ ndisplay = task->security;
+ if (ndisplay)
+ *ndisplay = *odisplay;
+ }
+
+ return 0;
}
void security_task_free(struct task_struct *task)
call_void_hook(cred_transfer, new, old);
}
-void security_cred_getsecid(const struct cred *c, u32 *secid)
+void security_cred_getsecid(const struct cred *c, struct lsmblob *blob)
{
- *secid = 0;
- call_void_hook(cred_getsecid, c, secid);
+ struct security_hook_list *hp;
+
+ lsmblob_init(blob, 0);
+ hlist_for_each_entry(hp, &security_hook_heads.cred_getsecid, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ hp->hook.cred_getsecid(c, &blob->secid[hp->lsmid->slot]);
+ }
}
EXPORT_SYMBOL(security_cred_getsecid);
-int security_kernel_act_as(struct cred *new, u32 secid)
+int security_kernel_act_as(struct cred *new, struct lsmblob *blob)
{
- return call_int_hook(kernel_act_as, 0, new, secid);
+ struct security_hook_list *hp;
+ int rc;
+
+ hlist_for_each_entry(hp, &security_hook_heads.kernel_act_as, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ rc = hp->hook.kernel_act_as(new, blob->secid[hp->lsmid->slot]);
+ if (rc != 0)
+ return rc;
+ }
+ return 0;
}
int security_kernel_create_files_as(struct cred *new, struct inode *inode)
return call_int_hook(task_getsid, 0, p);
}
-void security_task_getsecid_subj(struct task_struct *p, u32 *secid)
+void security_task_getsecid_subj(struct task_struct *p, struct lsmblob *blob)
{
- *secid = 0;
- call_void_hook(task_getsecid_subj, p, secid);
+ struct security_hook_list *hp;
+
+ lsmblob_init(blob, 0);
+ hlist_for_each_entry(hp, &security_hook_heads.task_getsecid_subj, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ hp->hook.task_getsecid_subj(p, &blob->secid[hp->lsmid->slot]);
+ }
}
EXPORT_SYMBOL(security_task_getsecid_subj);
-void security_task_getsecid_obj(struct task_struct *p, u32 *secid)
+void security_task_getsecid_obj(struct task_struct *p, struct lsmblob *blob)
{
- *secid = 0;
- call_void_hook(task_getsecid_obj, p, secid);
+ struct security_hook_list *hp;
+
+ lsmblob_init(blob, 0);
+ hlist_for_each_entry(hp, &security_hook_heads.task_getsecid_obj, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ hp->hook.task_getsecid_obj(p, &blob->secid[hp->lsmid->slot]);
+ }
}
EXPORT_SYMBOL(security_task_getsecid_obj);
return call_int_hook(ipc_permission, 0, ipcp, flag);
}
-void security_ipc_getsecid(struct kern_ipc_perm *ipcp, u32 *secid)
+void security_ipc_getsecid(struct kern_ipc_perm *ipcp, struct lsmblob *blob)
{
- *secid = 0;
- call_void_hook(ipc_getsecid, ipcp, secid);
+ struct security_hook_list *hp;
+
+ lsmblob_init(blob, 0);
+ hlist_for_each_entry(hp, &security_hook_heads.ipc_getsecid, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ hp->hook.ipc_getsecid(ipcp, &blob->secid[hp->lsmid->slot]);
+ }
}
int security_msg_msg_alloc(struct msg_msg *msg)
char **value)
{
struct security_hook_list *hp;
+ int display = lsm_task_display(current);
+ int slot = 0;
+
+ if (!strcmp(name, "display")) {
+ /*
+ * lsm_slot will be 0 if there are no displaying modules.
+ */
+ if (lsm_slot == 0)
+ return -EINVAL;
+
+ /*
+ * Only allow getting the current process' display.
+ * There are too few reasons to get another process'
+ * display and too many LSM policy issues.
+ */
+ if (current != p)
+ return -EINVAL;
+
+ display = lsm_task_display(p);
+ if (display != LSMBLOB_INVALID)
+ slot = display;
+ *value = kstrdup(lsm_slotlist[slot]->lsm, GFP_KERNEL);
+ if (*value)
+ return strlen(*value);
+ return -ENOMEM;
+ }
hlist_for_each_entry(hp, &security_hook_heads.getprocattr, list) {
- if (lsm != NULL && strcmp(lsm, hp->lsm))
+ if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
+ continue;
+ if (lsm == NULL && display != LSMBLOB_INVALID &&
+ display != hp->lsmid->slot)
continue;
return hp->hook.getprocattr(p, name, value);
}
return LSM_RET_DEFAULT(getprocattr);
}
+/**
+ * security_setprocattr - Set process attributes via /proc
+ * @lsm: name of module involved, or NULL
+ * @name: name of the attribute
+ * @value: value to set the attribute to
+ * @size: size of the value
+ *
+ * Set the process attribute for the specified security module
+ * to the specified value. Note that this can only be used to set
+ * the process attributes for the current, or "self" process.
+ * The /proc code has already done this check.
+ *
+ * Returns 0 on success, an appropriate code otherwise.
+ */
int security_setprocattr(const char *lsm, const char *name, void *value,
size_t size)
{
struct security_hook_list *hp;
+ char *termed;
+ char *copy;
+ int *display = current->security;
+ int rc = -EINVAL;
+ int slot = 0;
+
+ if (!strcmp(name, "display")) {
+ /*
+ * Change the "display" value only if all the security
+ * modules that support setting a procattr allow it.
+ * It is assumed that all such security modules will be
+ * cooperative.
+ */
+ if (size == 0)
+ return -EINVAL;
+
+ hlist_for_each_entry(hp, &security_hook_heads.setprocattr,
+ list) {
+ rc = hp->hook.setprocattr(name, value, size);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = -EINVAL;
+
+ copy = kmemdup_nul(value, size, GFP_KERNEL);
+ if (copy == NULL)
+ return -ENOMEM;
+
+ termed = strsep(©, " \n");
+
+ for (slot = 0; slot < lsm_slot; slot++)
+ if (!strcmp(termed, lsm_slotlist[slot]->lsm)) {
+ *display = lsm_slotlist[slot]->slot;
+ rc = size;
+ break;
+ }
+
+ kfree(termed);
+ return rc;
+ }
hlist_for_each_entry(hp, &security_hook_heads.setprocattr, list) {
- if (lsm != NULL && strcmp(lsm, hp->lsm))
+ if (lsm != NULL && strcmp(lsm, hp->lsmid->lsm))
+ continue;
+ if (lsm == NULL && *display != LSMBLOB_INVALID &&
+ *display != hp->lsmid->slot)
continue;
return hp->hook.setprocattr(name, value, size);
}
}
EXPORT_SYMBOL(security_ismaclabel);
-int security_secid_to_secctx(u32 secid, char **secdata, u32 *seclen)
+int security_secid_to_secctx(struct lsmblob *blob, char **secdata, u32 *seclen)
{
struct security_hook_list *hp;
- int rc;
+ int display = lsm_task_display(current);
- /*
- * Currently, only one LSM can implement secid_to_secctx (i.e this
- * LSM hook is not "stackable").
- */
hlist_for_each_entry(hp, &security_hook_heads.secid_to_secctx, list) {
- rc = hp->hook.secid_to_secctx(secid, secdata, seclen);
- if (rc != LSM_RET_DEFAULT(secid_to_secctx))
- return rc;
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
+ return hp->hook.secid_to_secctx(
+ blob->secid[hp->lsmid->slot],
+ secdata, seclen);
}
return LSM_RET_DEFAULT(secid_to_secctx);
}
EXPORT_SYMBOL(security_secid_to_secctx);
-int security_secctx_to_secid(const char *secdata, u32 seclen, u32 *secid)
+int security_secctx_to_secid(const char *secdata, u32 seclen,
+ struct lsmblob *blob)
{
- *secid = 0;
- return call_int_hook(secctx_to_secid, 0, secdata, seclen, secid);
+ struct security_hook_list *hp;
+ int display = lsm_task_display(current);
+
+ lsmblob_init(blob, 0);
+ hlist_for_each_entry(hp, &security_hook_heads.secctx_to_secid, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
+ return hp->hook.secctx_to_secid(secdata, seclen,
+ &blob->secid[hp->lsmid->slot]);
+ }
+ return 0;
}
EXPORT_SYMBOL(security_secctx_to_secid);
void security_release_secctx(char *secdata, u32 seclen)
{
- call_void_hook(release_secctx, secdata, seclen);
+ struct security_hook_list *hp;
+ int display = lsm_task_display(current);
+
+ hlist_for_each_entry(hp, &security_hook_heads.release_secctx, list)
+ if (display == LSMBLOB_INVALID || display == hp->lsmid->slot) {
+ hp->hook.release_secctx(secdata, seclen);
+ return;
+ }
}
EXPORT_SYMBOL(security_release_secctx);
int security_socket_getpeersec_stream(struct socket *sock, char __user *optval,
int __user *optlen, unsigned len)
{
- return call_int_hook(socket_getpeersec_stream, -ENOPROTOOPT, sock,
- optval, optlen, len);
+ int display = lsm_task_display(current);
+ struct security_hook_list *hp;
+
+ hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_stream,
+ list)
+ if (display == LSMBLOB_INVALID || display == hp->lsmid->slot)
+ return hp->hook.socket_getpeersec_stream(sock, optval,
+ optlen, len);
+ return -ENOPROTOOPT;
}
-int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb, u32 *secid)
+int security_socket_getpeersec_dgram(struct socket *sock, struct sk_buff *skb,
+ struct lsmblob *blob)
{
- return call_int_hook(socket_getpeersec_dgram, -ENOPROTOOPT, sock,
- skb, secid);
+ struct security_hook_list *hp;
+ int rc = -ENOPROTOOPT;
+
+ hlist_for_each_entry(hp, &security_hook_heads.socket_getpeersec_dgram,
+ list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ rc = hp->hook.socket_getpeersec_dgram(sock, skb,
+ &blob->secid[hp->lsmid->slot]);
+ if (rc != 0)
+ break;
+ }
+ return rc;
}
EXPORT_SYMBOL(security_socket_getpeersec_dgram);
int security_sk_alloc(struct sock *sk, int family, gfp_t priority)
{
- return call_int_hook(sk_alloc_security, 0, sk, family, priority);
+ int rc = lsm_sock_alloc(sk, priority);
+
+ if (unlikely(rc))
+ return rc;
+ rc = call_int_hook(sk_alloc_security, 0, sk, family, priority);
+ if (unlikely(rc))
+ security_sk_free(sk);
+ return rc;
}
void security_sk_free(struct sock *sk)
{
call_void_hook(sk_free_security, sk);
+ kfree(sk->sk_security);
+ sk->sk_security = NULL;
}
void security_sk_clone(const struct sock *sk, struct sock *newsk)
}
EXPORT_SYMBOL(security_inet_conn_established);
-int security_secmark_relabel_packet(u32 secid)
+int security_secmark_relabel_packet(struct lsmblob *blob)
{
- return call_int_hook(secmark_relabel_packet, 0, secid);
+ struct security_hook_list *hp;
+ int rc = 0;
+
+ hlist_for_each_entry(hp, &security_hook_heads.secmark_relabel_packet,
+ list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ rc = hp->hook.secmark_relabel_packet(
+ blob->secid[hp->lsmid->slot]);
+ if (rc != 0)
+ break;
+ }
+ return rc;
}
EXPORT_SYMBOL(security_secmark_relabel_packet);
int security_audit_rule_init(u32 field, u32 op, char *rulestr, void **lsmrule)
{
- return call_int_hook(audit_rule_init, 0, field, op, rulestr, lsmrule);
+ struct security_hook_list *hp;
+ bool one_is_good = false;
+ int rc = 0;
+ int trc;
+
+ hlist_for_each_entry(hp, &security_hook_heads.audit_rule_init, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ trc = hp->hook.audit_rule_init(field, op, rulestr,
+ &lsmrule[hp->lsmid->slot]);
+ if (trc == 0)
+ one_is_good = true;
+ else
+ rc = trc;
+ }
+ if (one_is_good)
+ return 0;
+ return rc;
}
int security_audit_rule_known(struct audit_krule *krule)
return call_int_hook(audit_rule_known, 0, krule);
}
-void security_audit_rule_free(void *lsmrule)
+void security_audit_rule_free(void **lsmrule)
{
- call_void_hook(audit_rule_free, lsmrule);
+ struct security_hook_list *hp;
+
+ hlist_for_each_entry(hp, &security_hook_heads.audit_rule_free, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ if (lsmrule[hp->lsmid->slot] == NULL)
+ continue;
+ hp->hook.audit_rule_free(lsmrule[hp->lsmid->slot]);
+ }
}
-int security_audit_rule_match(u32 secid, u32 field, u32 op, void *lsmrule)
+int security_audit_rule_match(struct lsmblob *blob, u32 field, u32 op,
+ void **lsmrule)
{
- return call_int_hook(audit_rule_match, 0, secid, field, op, lsmrule);
+ struct security_hook_list *hp;
+ int rc;
+
+ hlist_for_each_entry(hp, &security_hook_heads.audit_rule_match, list) {
+ if (WARN_ON(hp->lsmid->slot < 0 || hp->lsmid->slot >= lsm_slot))
+ continue;
+ if (lsmrule[hp->lsmid->slot] == NULL)
+ continue;
+ rc = hp->hook.audit_rule_match(blob->secid[hp->lsmid->slot],
+ field, op,
+ &lsmrule[hp->lsmid->slot]);
+ if (rc)
+ return rc;
+ }
+ return 0;
}
#endif /* CONFIG_AUDIT */