[WIP][RFC][PATCH 3/3] security: add infoflow LSM
Roberto Sassu
roberto.sassu at huawei.com
Sun Aug 18 23:57:45 UTC 2019
Add a new security module called Infoflow LSM to enforce the Clark-Wilson
integrity policy on top of existing label-based LSMs, such as SELinux and
SMACK.
Signed-off-by: Roberto Sassu <roberto.sassu at huawei.com>
---
.../admin-guide/kernel-parameters.txt | 23 +
include/linux/lsm_audit.h | 3 +
include/uapi/linux/xattr.h | 2 +
security/Kconfig | 1 +
security/Makefile | 2 +
security/infoflow/Kconfig | 6 +
security/infoflow/Makefile | 7 +
security/infoflow/infoflow.h | 173 ++++
security/infoflow/infoflow_access.c | 182 ++++
security/infoflow/infoflow_ctx.c | 342 ++++++++
security/infoflow/infoflow_fs.c | 479 +++++++++++
security/infoflow/infoflow_lsm.c | 778 ++++++++++++++++++
security/integrity/evm/evm_main.c | 1 +
13 files changed, 1999 insertions(+)
create mode 100644 security/infoflow/Kconfig
create mode 100644 security/infoflow/Makefile
create mode 100644 security/infoflow/infoflow.h
create mode 100644 security/infoflow/infoflow_access.c
create mode 100644 security/infoflow/infoflow_ctx.c
create mode 100644 security/infoflow/infoflow_fs.c
create mode 100644 security/infoflow/infoflow_lsm.c
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 47311cdf63d9..011b092f667a 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -1673,6 +1673,29 @@
different crypto accelerators. This option can be used
to achieve best performance for particular HW.
+ infoflow= [INFOFLOW] Disable or enable Infoflow LSM at boot time.
+ Format: { "0" | "1" }
+ 0 -- disable.
+ 1 -- enable.
+
+ infoflow_mode= [INFOFLOW] Set Infoflow LSM mode.
+ Format: { "disabled" | "discover" | "enforce" |
+ "enforce-audit" };
+ disabled -- LSM disabled.
+ discover -- record operations and labels set by
+ a parent LSM (e.g. SELinux, SMACK).
+ enforce -- enforce Clark-Wilson integrity policy.
+ enforce-audit -- enforce Clark-Wilson integrity policy
+ and record denied operations.
+ permissive -- check Clark-Wilson integrity policy
+ but don't enforce decision.
+ permissive-audit -- check Clark-Wilson integrity policy
+ and record decision.
+
+ infoflow_promote [INFOFLOW] Promote low integrity objects that would
+ cause a read-down violation of the
+ Clark-Wilson integrity policy.
+
init= [KNL]
Format: <full_path>
Run specified binary instead of /sbin/init as init
diff --git a/include/linux/lsm_audit.h b/include/linux/lsm_audit.h
index 915330abf6e5..a955087c2283 100644
--- a/include/linux/lsm_audit.h
+++ b/include/linux/lsm_audit.h
@@ -104,6 +104,9 @@ struct common_audit_data {
#endif
#ifdef CONFIG_SECURITY_APPARMOR
struct apparmor_audit_data *apparmor_audit_data;
+#endif
+#ifdef CONFIG_SECURITY_INFOFLOW
+ struct infoflow_audit_data *infoflow_audit_data;
#endif
}; /* per LSM data pointer union */
};
diff --git a/include/uapi/linux/xattr.h b/include/uapi/linux/xattr.h
index c1395b5bd432..ccdacb5f6e2c 100644
--- a/include/uapi/linux/xattr.h
+++ b/include/uapi/linux/xattr.h
@@ -77,5 +77,7 @@
#define XATTR_POSIX_ACL_DEFAULT "posix_acl_default"
#define XATTR_NAME_POSIX_ACL_DEFAULT XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_DEFAULT
+#define XATTR_INFOFLOW_SUFFIX "infoflow"
+#define XATTR_NAME_INFOFLOW XATTR_SECURITY_PREFIX XATTR_INFOFLOW_SUFFIX
#endif /* _UAPI_LINUX_XATTR_H */
diff --git a/security/Kconfig b/security/Kconfig
index 466cc1f8ffed..7c5f0dc80b7b 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -237,6 +237,7 @@ source "security/apparmor/Kconfig"
source "security/loadpin/Kconfig"
source "security/yama/Kconfig"
source "security/safesetid/Kconfig"
+source "security/infoflow/Kconfig"
source "security/integrity/Kconfig"
diff --git a/security/Makefile b/security/Makefile
index c598b904938f..1f33df7966dd 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -11,6 +11,7 @@ subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor
subdir-$(CONFIG_SECURITY_YAMA) += yama
subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin
subdir-$(CONFIG_SECURITY_SAFESETID) += safesetid
+subdir-$(CONFIG_SECURITY_INFOFLOW) += infoflow
# always enable default capabilities
obj-y += commoncap.o
@@ -27,6 +28,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/
obj-$(CONFIG_SECURITY_SAFESETID) += safesetid/
+obj-$(CONFIG_SECURITY_INFOFLOW) += infoflow/
obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o
# Object integrity file lists
diff --git a/security/infoflow/Kconfig b/security/infoflow/Kconfig
new file mode 100644
index 000000000000..744a28436e34
--- /dev/null
+++ b/security/infoflow/Kconfig
@@ -0,0 +1,6 @@
+config SECURITY_INFOFLOW
+ bool "Infoflow LSM"
+ select SECURITYFS
+ help
+ Infoflow LSM enforces the Clark-Wilson policy on top of existing LSMs,
+ such as SELinux and SMACK.
diff --git a/security/infoflow/Makefile b/security/infoflow/Makefile
new file mode 100644
index 000000000000..521a3c0a79bb
--- /dev/null
+++ b/security/infoflow/Makefile
@@ -0,0 +1,7 @@
+#
+# Makefile for Infoflow LSM
+#
+
+obj-$(CONFIG_SECURITY_INFOFLOW) := infoflow.o
+
+infoflow-y := infoflow_lsm.o infoflow_ctx.o infoflow_access.o infoflow_fs.o
diff --git a/security/infoflow/infoflow.h b/security/infoflow/infoflow.h
new file mode 100644
index 000000000000..26d35718e777
--- /dev/null
+++ b/security/infoflow/infoflow.h
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018,2019 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu at huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: infoflow.h
+ * Header file.
+ */
+
+#ifndef __INFOFLOW_H
+#define __INFOFLOW_H
+
+#include <linux/lsm_hooks.h>
+#include <linux/lsm_audit.h>
+#include <linux/msg.h>
+#include <net/flow.h>
+#include <net/sock.h>
+
+enum infoflow_class {CLASS_LNK_FILE, CLASS_REG_FILE, CLASS_DIR,
+ CLASS_CHR_FILE, CLASS_BLK_FILE, CLASS_FIFO_FILE,
+ CLASS_SOCK_FILE, CLASS_IPC, CLASS_MSGQ, CLASS_SHM,
+ CLASS_SEM, CLASS_SOCKET, CLASS_KEY, CLASS_PROCESS,
+ CLASS_KERNEL, CLASS_MODULE, CLASS_UNDEFINED, CLASS__LAST};
+
+struct infoflow_class_desc {
+ const char *name;
+ u8 excluded;
+};
+
+extern struct infoflow_class_desc infoflow_class_array[CLASS__LAST];
+
+extern struct list_head contexts;
+
+#define INFOFLOW_PARENT_LSM_INIT 0x1
+#define INFOFLOW_POLICY_INIT 0x2
+#define INFOFLOW_PROMOTE 0x4
+extern int infoflow_init_flags;
+
+enum infoflow_modes { INFOFLOW_DISABLED, INFOFLOW_DISCOVER, INFOFLOW_ENFORCE,
+ INFOFLOW_ENFORCE_AUDIT, INFOFLOW_PERMISSIVE,
+ INFOFLOW_PERMISSIVE_AUDIT, INFOFLOW_MODE__LAST };
+
+extern const char *infoflow_modes_str[INFOFLOW_MODE__LAST];
+extern int infoflow_mode;
+
+#define CTX_FLAG_TCB 0x01
+#define CTX_FLAG_FILTER 0x02
+#define CTX_FLAG_INITIALIZED 0x04
+#define CTX_FLAG_CANNOT_PROMOTE 0x08
+struct infoflow_ctx {
+ struct rb_node rb_node;
+ struct list_head access_subjs;
+ struct list_head filter_subjs;
+ struct list_head context_list;
+ spinlock_t ctx_lock;
+ u32 sid;
+ u8 flags;
+ enum infoflow_class class;
+ char *label;
+ int label_len;
+};
+
+#define TYPE_RULE 0
+#define TYPE_FILTER 1
+struct infoflow_subj_desc {
+ struct list_head list;
+ struct infoflow_ctx *ctx;
+ int mask;
+ u8 denied;
+ const char *cause;
+};
+
+struct infoflow_audit_data {
+ struct infoflow_ctx *subj;
+ struct infoflow_ctx *obj;
+ int request;
+ int result;
+ char *cause;
+};
+
+static inline u16 infoflow_inode_class(umode_t mode)
+{
+ switch (mode & S_IFMT) {
+ case S_IFSOCK:
+ return CLASS_SOCK_FILE;
+ case S_IFLNK:
+ return CLASS_LNK_FILE;
+ case S_IFREG:
+ return CLASS_REG_FILE;
+ case S_IFBLK:
+ return CLASS_BLK_FILE;
+ case S_IFDIR:
+ return CLASS_DIR;
+ case S_IFCHR:
+ return CLASS_CHR_FILE;
+ case S_IFIFO:
+ return CLASS_FIFO_FILE;
+ }
+
+ return CLASS_UNDEFINED;
+}
+
+static inline u32 infoflow_file_f_mode_to_mask(struct file *file)
+{
+ int mask = 0;
+
+ if (file->f_mode & FMODE_READ)
+ mask |= MAY_READ;
+ if (file->f_mode & FMODE_WRITE)
+ mask |= MAY_WRITE;
+
+ return mask;
+}
+
+static inline enum infoflow_class infoflow_lookup_class(char *class)
+{
+ int i;
+
+ for (i = 0; i < CLASS__LAST; i++) {
+ if (!strcmp(infoflow_class_array[i].name, class))
+ break;
+ }
+
+ return i;
+}
+
+struct file_security_struct {
+ u32 sid;
+ u32 isid;
+ int pseqno;
+};
+
+extern struct lsm_blob_sizes infoflow_blob_sizes;
+
+static inline u8 *infoflow_inode(const struct inode *inode)
+{
+ return inode->i_security + infoflow_blob_sizes.lbs_inode;
+}
+
+static inline struct file_security_struct *infoflow_file(
+ const struct file *file)
+{
+ return file->f_security + infoflow_blob_sizes.lbs_file;
+}
+
+struct infoflow_ctx *infoflow_ctx_find_sid(enum infoflow_class class,
+ u32 sid);
+struct infoflow_ctx *infoflow_ctx_insert_sid(enum infoflow_class class, u32 sid,
+ u8 flags);
+struct infoflow_ctx *infoflow_ctx_insert_label(enum infoflow_class class,
+ char *label, int label_len,
+ u8 flags);
+void infoflow_ctx_delete(void);
+void infoflow_ctx_update_sid(void);
+int infoflow_ctx_find_add_subj(enum infoflow_class sclass, u32 ssid,
+ struct infoflow_ctx **sctx, u8 sflags,
+ enum infoflow_class oclass, u32 osid,
+ struct infoflow_ctx **octx, u8 oflags,
+ int mask, u8 denied, const char *cause,
+ int type);
+
+int infoflow_allow_access(enum infoflow_class sclass, u32 ssid,
+ enum infoflow_class oclass, u32 osid,
+ u8 *inode_flags, int mask,
+ struct common_audit_data *a);
+
+#endif /*__INFOFLOW_H*/
diff --git a/security/infoflow/infoflow_access.c b/security/infoflow/infoflow_access.c
new file mode 100644
index 000000000000..de81f0bfaad7
--- /dev/null
+++ b/security/infoflow/infoflow_access.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018,2019 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu at huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: infoflow_access.c
+ * Functions to enforce Clark-Wilson policy and log decisions.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/cred.h>
+#include <linux/magic.h>
+#include <linux/audit.h>
+#include <linux/xattr.h>
+
+#include "infoflow.h"
+
+struct infoflow_ctx unknown_ctx = { .class = CLASS_UNDEFINED,
+ .label = "unknown_label" };
+
+static void infoflow_log_callback(struct audit_buffer *ab, void *a)
+{
+ struct common_audit_data *ad = a;
+ struct infoflow_audit_data *sad = ad->infoflow_audit_data;
+
+ audit_log_format(ab, "lsm=infoflow request=%d action=%s cause=%s",
+ sad->request, sad->result ? "denied" : "granted",
+ sad->cause);
+ audit_log_format(ab, " subject=%s object=%s oclass=%s",
+ sad->subj->label, sad->obj->label,
+ infoflow_class_array[sad->obj->class].name);
+}
+
+static void infoflow_log(struct infoflow_ctx *subj, struct infoflow_ctx *obj,
+ int request, int result, char *cause,
+ struct common_audit_data *a)
+{
+ struct infoflow_audit_data sad;
+
+ memset(&sad, 0, sizeof(sad));
+
+ sad.subj = subj;
+ if (!sad.subj)
+ sad.subj = &unknown_ctx;
+ sad.obj = obj;
+ if (!sad.obj)
+ sad.obj = &unknown_ctx;
+ sad.request = request;
+ sad.result = result;
+ sad.cause = cause;
+
+ a->infoflow_audit_data = &sad;
+
+ common_lsm_audit(a, infoflow_log_callback, NULL);
+}
+
+/**
+ * infoflow_allow_access - enforce Clark-Wilson policy
+ * @sclass: subject class
+ * @ssid: subject's security identifier
+ * @oclass: object class
+ * @osid: object's security identifier
+ * @inode_flags: inode's flags
+ * @mask: operations requested
+ * @a: audit structure
+ *
+ * Return 0 on success, a negative value on failure.
+ */
+int infoflow_allow_access(enum infoflow_class sclass, u32 ssid,
+ enum infoflow_class oclass, u32 osid,
+ u8 *inode_flags, int mask,
+ struct common_audit_data *a)
+{
+ struct infoflow_subj_desc *desc, *found_desc = NULL;
+ struct infoflow_ctx *sctx = NULL, *octx = NULL;
+ u8 sflags = 0, oflags = inode_flags ? *inode_flags : 0;
+ bool denied = false;
+ char *cause = "unknown";
+ int result;
+
+ if (infoflow_class_array[oclass].excluded)
+ return 0;
+
+ if (!(infoflow_init_flags & INFOFLOW_PARENT_LSM_INIT))
+ return 0;
+
+ if (infoflow_mode == INFOFLOW_DISCOVER) {
+ result = infoflow_ctx_find_add_subj(sclass, ssid, &sctx, 0,
+ oclass, osid, &octx, 0,
+ mask, 0, NULL, TYPE_RULE);
+ if (result < 0)
+ pr_err("Cannot add rule for sclass %d, ssid %d, "
+ "oclass %d, osid %d\n", sclass, ssid, oclass,
+ osid);
+
+ return 0;
+ }
+
+ if (!(infoflow_init_flags & INFOFLOW_POLICY_INIT))
+ return 0;
+
+ sctx = infoflow_ctx_find_sid(sclass, ssid);
+ if (sctx)
+ sflags |= sctx->flags;
+
+ octx = infoflow_ctx_find_sid(oclass, osid);
+ if (octx)
+ oflags |= octx->flags;
+
+ if (mask & MAY_WRITE && !(sflags & CTX_FLAG_TCB) &&
+ !(oflags & CTX_FLAG_TCB) && inode_flags)
+ *inode_flags |= CTX_FLAG_CANNOT_PROMOTE;
+
+ if ((mask & MAY_WRITE) && !(sflags & CTX_FLAG_TCB) &&
+ (oflags & CTX_FLAG_TCB) && !(oflags & CTX_FLAG_FILTER)) {
+ denied = true;
+ cause = "biba-write-up";
+ }
+
+ if ((mask & (MAY_READ | MAY_EXEC)) && (sflags & CTX_FLAG_TCB) &&
+ !(oflags & CTX_FLAG_TCB) && !(oflags & CTX_FLAG_FILTER)) {
+ if ((infoflow_init_flags & INFOFLOW_PROMOTE) &&
+ !(mask & MAY_WRITE) &&
+ !(oflags & CTX_FLAG_CANNOT_PROMOTE) && inode_flags) {
+ *inode_flags |= CTX_FLAG_TCB;
+ } else {
+ denied = true;
+ cause = "biba-read-down";
+ }
+ }
+
+ if ((mask & (MAY_READ | MAY_EXEC)) && (sflags & CTX_FLAG_TCB) &&
+ !(oflags & CTX_FLAG_TCB) && (oflags & CTX_FLAG_FILTER)) {
+ if (list_empty(&octx->filter_subjs)) {
+ found_desc = (void *)1;
+ } else {
+ list_for_each_entry(desc, &octx->filter_subjs, list)
+ if (desc->ctx == sctx) {
+ found_desc = desc;
+ break;
+ }
+ }
+
+ if (!found_desc) {
+ denied = true;
+ cause = "filtering-subj-not-found";
+ }
+ }
+
+ if (!denied)
+ return 0;
+
+ if (infoflow_mode == INFOFLOW_ENFORCE_AUDIT ||
+ infoflow_mode == INFOFLOW_PERMISSIVE_AUDIT) {
+ result = infoflow_ctx_find_add_subj(sclass, ssid, &sctx, 0,
+ oclass, osid, &octx, 0,
+ mask, denied, cause,
+ TYPE_RULE);
+ if (result < 0)
+ pr_err("Cannot add rule for sclass %d, ssid %d,"
+ "oclass %d, osid %d\n", sclass, ssid,
+ oclass, osid);
+ }
+
+ result = 0;
+
+ if (infoflow_mode == INFOFLOW_ENFORCE ||
+ infoflow_mode == INFOFLOW_ENFORCE_AUDIT)
+ result = -EACCES;
+
+ if (a)
+ infoflow_log(sctx, octx, mask, result, cause, a);
+
+ return result;
+}
diff --git a/security/infoflow/infoflow_ctx.c b/security/infoflow/infoflow_ctx.c
new file mode 100644
index 000000000000..debec9268100
--- /dev/null
+++ b/security/infoflow/infoflow_ctx.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2008 IBM Corporation
+ * Copyright (C) 2018,2019 Huawei Technologies Duesseldorf GmbH
+ *
+ * Authors: Roberto Sassu <roberto.sassu at huawei.com>
+ * Mimi Zohar <zohar at us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: infoflow_ctx.c
+ * Functions to manage security contexts.
+ */
+
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/rbtree.h>
+
+#include "infoflow.h"
+
+static struct rb_root infoflow_ctx_tree[CLASS__LAST] = { RB_ROOT };
+static DEFINE_RWLOCK(infoflow_ctx_lock);
+LIST_HEAD(contexts);
+
+static struct infoflow_ctx *__infoflow_ctx_find_sid(enum infoflow_class class,
+ u32 sid)
+{
+ struct infoflow_ctx *ctx;
+ struct rb_node *n = infoflow_ctx_tree[class].rb_node;
+
+ while (n) {
+ ctx = rb_entry(n, struct infoflow_ctx, rb_node);
+
+ if (sid < ctx->sid)
+ n = n->rb_left;
+ else if (sid > ctx->sid)
+ n = n->rb_right;
+ else
+ break;
+ }
+ if (!n)
+ return NULL;
+
+ return ctx;
+}
+
+/*
+ * infoflow_ctx_find_sid - return the ctx associated with the class and sid
+ * @class: object class
+ * @sid: object's security identifier
+ *
+ * Return infoflow_ctx if found, NULL otherwise.
+ */
+struct infoflow_ctx *infoflow_ctx_find_sid(enum infoflow_class class, u32 sid)
+{
+ struct infoflow_ctx *ctx;
+
+ read_lock(&infoflow_ctx_lock);
+ ctx = __infoflow_ctx_find_sid(class, sid);
+ read_unlock(&infoflow_ctx_lock);
+
+ return ctx;
+}
+
+static struct infoflow_ctx *__infoflow_ctx_find_label(enum infoflow_class class,
+ char *label,
+ int label_len)
+{
+ struct infoflow_ctx *ctx;
+
+ list_for_each_entry(ctx, &contexts, context_list)
+ if (ctx->class == class && ctx->label_len == label_len &&
+ !strncmp(ctx->label, label, label_len))
+ return ctx;
+
+ return NULL;
+}
+
+static struct infoflow_ctx *infoflow_ctx_find_label(enum infoflow_class class,
+ char *label, int label_len)
+{
+ struct infoflow_ctx *ctx;
+
+ read_lock(&infoflow_ctx_lock);
+ ctx = __infoflow_ctx_find_label(class, label, label_len);
+ read_unlock(&infoflow_ctx_lock);
+
+ return ctx;
+}
+
+static void infoflow_ctx_insert_to_rbtree(struct infoflow_ctx *ctx)
+{
+ struct rb_node **p;
+ struct rb_node *node, *parent = NULL;
+ struct infoflow_ctx *test_ctx;
+
+ p = &infoflow_ctx_tree[ctx->class].rb_node;
+ while (*p) {
+ parent = *p;
+ test_ctx = rb_entry(parent, struct infoflow_ctx, rb_node);
+ if (ctx->sid < test_ctx->sid)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ node = &ctx->rb_node;
+ rb_link_node(node, parent, p);
+ rb_insert_color(node, &infoflow_ctx_tree[ctx->class]);
+}
+
+static struct infoflow_ctx *infoflow_ctx_insert(enum infoflow_class class,
+ u32 sid, u8 flags, char *label,
+ int label_len)
+{
+ struct infoflow_ctx *ctx;
+
+ ctx = kmalloc(sizeof(*ctx), GFP_ATOMIC | __GFP_NOWARN);
+ if (!ctx)
+ return ERR_PTR(-ENOMEM);
+
+ ctx->sid = sid;
+ ctx->flags = flags;
+ ctx->class = class;
+ ctx->label = kmemdup_nul(label, label_len, GFP_ATOMIC | __GFP_NOWARN);
+ if (!ctx->label) {
+ kfree(ctx);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ ctx->label_len = label_len;
+
+ INIT_LIST_HEAD(&ctx->access_subjs);
+ INIT_LIST_HEAD(&ctx->filter_subjs);
+ spin_lock_init(&ctx->ctx_lock);
+
+ write_lock(&infoflow_ctx_lock);
+ infoflow_ctx_insert_to_rbtree(ctx);
+ list_add_tail_rcu(&ctx->context_list, &contexts);
+ write_unlock(&infoflow_ctx_lock);
+
+ return ctx;
+}
+
+/**
+ * infoflow_ctx_insert_sid - insert ctx from sid
+ * @class: object class
+ * @sid: object's security identifier
+ * @flags: flags associated to the sid
+ *
+ * Return new infoflow_ctx on success, NULL on error.
+ */
+struct infoflow_ctx *infoflow_ctx_insert_sid(enum infoflow_class class, u32 sid,
+ u8 flags)
+{
+ char *label;
+ int label_len;
+ int result;
+ struct infoflow_ctx *ctx;
+
+ ctx = infoflow_ctx_find_sid(class, sid);
+ if (ctx) {
+ ctx->flags |= flags;
+ return ctx;
+ }
+
+ result = security_secid_to_secctx(sid, &label, &label_len);
+ if (result < 0) {
+ pr_err("Cannot retrieve label for sid %d\n", sid);
+ return ERR_PTR(-EINVAL);
+ }
+
+ ctx = infoflow_ctx_insert(class, sid, flags, label, label_len);
+
+ security_release_secctx(label, label_len);
+ return ctx;
+}
+
+/**
+ * infoflow_ctx_insert_label - insert ctx from label
+ * @class: object class
+ * @label: label associated to the sid
+ * @label_len: label length
+ * @flags: flags associated to the sid
+ *
+ * Return new infoflow_ctx on success, NULL on error.
+ */
+struct infoflow_ctx *infoflow_ctx_insert_label(enum infoflow_class class,
+ char *label, int label_len,
+ u8 flags)
+{
+ struct infoflow_ctx *ctx;
+ u32 sid = 0;
+ int result;
+
+ ctx = infoflow_ctx_find_label(class, label, label_len);
+ if (ctx) {
+ ctx->flags |= flags;
+ return ctx;
+ }
+
+ if (!(infoflow_init_flags & INFOFLOW_PARENT_LSM_INIT))
+ goto out;
+
+ result = security_secctx_to_secid(label, label_len, &sid);
+ if (result < 0) {
+ pr_err("Cannot retrieve sid for label %s\n", label);
+ return ERR_PTR(-EINVAL);
+ }
+out:
+ return infoflow_ctx_insert(class, sid, flags, label, label_len);
+}
+
+/**
+ * infoflow_ctx_delete - delete all contexts
+ *
+ */
+void infoflow_ctx_delete(void)
+{
+ struct infoflow_ctx *ctx, *tmp_ctx;
+ int i;
+
+ write_lock(&infoflow_ctx_lock);
+ list_for_each_entry_safe(ctx, tmp_ctx, &contexts, context_list) {
+ list_del(&ctx->context_list);
+ kfree(ctx->label);
+ kfree(ctx);
+ }
+
+ for (i = 0; i < CLASS__LAST; i++)
+ infoflow_ctx_tree[i] = RB_ROOT;
+
+ write_unlock(&infoflow_ctx_lock);
+}
+
+/**
+ * infoflow_ctx_update_sid - update infoflow_ctx SID after policy change
+ *
+ */
+void infoflow_ctx_update_sid(void)
+{
+ struct infoflow_ctx *ctx;
+ u32 new_sid;
+ int result;
+
+ list_for_each_entry(ctx, &contexts, context_list) {
+ result = security_secctx_to_secid(ctx->label,
+ strlen(ctx->label),
+ &new_sid);
+ if (result < 0) {
+ pr_err("Cannot obtain SID for context %s\n",
+ ctx->label);
+ continue;
+ }
+
+ if (ctx->sid == new_sid)
+ continue;
+
+ write_lock(&infoflow_ctx_lock);
+ rb_erase(&ctx->rb_node, &infoflow_ctx_tree[ctx->class]);
+
+ ctx->sid = new_sid;
+ infoflow_ctx_insert_to_rbtree(ctx);
+ write_unlock(&infoflow_ctx_lock);
+ }
+}
+
+/**
+ * infoflow_ctx_find_add_subj - add discovered interaction or filtering subj
+ * @sclass: subject class
+ * @ssid: subject's security identifier
+ * @sctx: subject's infoflow_ctx
+ * @sflags: subject's flags
+ * @oclass: object class
+ * @osid: object's security identifier
+ * @octx: object's infoflow_ctx
+ * @oflags: object's flags
+ * @mask: operations requested
+ * @denied: whether access is denied
+ * @cause: reason if access is denied
+ * @type: type of information (rule, filtering subj)
+ *
+ * Return 0 on success, a negative value on failure.
+ */
+int infoflow_ctx_find_add_subj(enum infoflow_class sclass, u32 ssid,
+ struct infoflow_ctx **sctx, u8 sflags,
+ enum infoflow_class oclass, u32 osid,
+ struct infoflow_ctx **octx, u8 oflags,
+ int mask, u8 denied, const char *cause,
+ int type)
+{
+ struct infoflow_subj_desc *desc, *found_desc = NULL;
+ struct list_head *head;
+
+ if (!*sctx) {
+ *sctx = infoflow_ctx_insert_sid(sclass, ssid, sflags);
+ if (IS_ERR(*sctx))
+ return PTR_ERR(*sctx);
+ }
+
+ if (!*octx) {
+ *octx = infoflow_ctx_insert_sid(oclass, osid, oflags);
+ if (IS_ERR(*octx))
+ return PTR_ERR(*octx);
+ }
+
+ head = &(*octx)->access_subjs;
+ if (type == TYPE_FILTER)
+ head = &(*octx)->filter_subjs;
+
+ list_for_each_entry(desc, head, list) {
+ if (desc->ctx == *sctx) {
+ found_desc = desc;
+ break;
+ }
+ }
+
+ if (!found_desc) {
+ desc = kmalloc(sizeof(*desc), GFP_ATOMIC | __GFP_NOWARN);
+ if (!desc) {
+ pr_err("Cannot allocate memory for subject desc\n");
+ return -ENOMEM;
+ }
+
+ desc->ctx = *sctx;
+ desc->mask = 0;
+ desc->denied = 0;
+ desc->cause = cause;
+
+ spin_lock(&(*octx)->ctx_lock);
+ list_add_tail_rcu(&desc->list, head);
+ spin_unlock(&(*octx)->ctx_lock);
+ }
+
+ desc->mask |= mask;
+ desc->denied |= denied;
+ return 0;
+}
diff --git a/security/infoflow/infoflow_fs.c b/security/infoflow/infoflow_fs.c
new file mode 100644
index 000000000000..52be308d0358
--- /dev/null
+++ b/security/infoflow/infoflow_fs.c
@@ -0,0 +1,479 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2008 IBM Corporation
+ * Copyright (C) 2018,2019 Huawei Technologies Duesseldorf GmbH
+ *
+ * Authors: Roberto Sassu <roberto.sassu at huawei.com>
+ * Mimi Zohar <zohar at us.ibm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: infoflow_fs.c
+ * Functions implementing methods for securityfs.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/seq_file.h>
+#include <linux/security.h>
+#include <linux/parser.h>
+
+#include "infoflow.h"
+
+static struct dentry *infoflow_dir;
+static struct dentry *infoflow_rules;
+static struct dentry *infoflow_policy;
+static struct dentry *infoflow_enforce;
+
+static void *infoflow_ctx_start(struct seq_file *m, loff_t *pos)
+{
+ loff_t l = *pos;
+ struct infoflow_ctx *ctx;
+
+ if (m->file->f_path.dentry == infoflow_policy &&
+ !(infoflow_init_flags & INFOFLOW_POLICY_INIT))
+ return NULL;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(ctx, &contexts, context_list) {
+ if (!l--) {
+ rcu_read_unlock();
+ return ctx;
+ }
+ }
+ rcu_read_unlock();
+ return NULL;
+}
+
+static void *infoflow_ctx_next(struct seq_file *m, void *v, loff_t *pos)
+{
+ struct infoflow_ctx *ctx = v;
+
+ rcu_read_lock();
+ ctx = list_entry_rcu(ctx->context_list.next, struct infoflow_ctx,
+ context_list);
+ rcu_read_unlock();
+ (*pos)++;
+
+ return (&ctx->context_list == &contexts) ? NULL : ctx;
+}
+
+static void infoflow_ctx_stop(struct seq_file *m, void *v)
+{
+}
+
+int infoflow_rules_show(struct seq_file *m, void *v)
+{
+ struct infoflow_ctx *ctx = v;
+ struct infoflow_subj_desc *desc;
+
+ list_for_each_entry(desc, &ctx->access_subjs, list) {
+ seq_printf(m, "allow %s %s:%s {", desc->ctx->label, ctx->label,
+ infoflow_class_array[ctx->class].name);
+
+ if (desc->mask & MAY_READ || desc->mask & MAY_EXEC)
+ seq_printf(m, " read");
+ if (desc->mask & MAY_WRITE || desc->mask & MAY_APPEND)
+ seq_printf(m, " write");
+
+ seq_printf(m, " };");
+
+ if (desc->denied)
+ seq_printf(m, " [denied:%s]", desc->cause);
+
+ seq_printf(m, "\n");
+ }
+
+ return 0;
+}
+
+static const struct seq_operations infoflow_rules_seqops = {
+ .start = infoflow_ctx_start,
+ .next = infoflow_ctx_next,
+ .stop = infoflow_ctx_stop,
+ .show = infoflow_rules_show,
+};
+
+static int infoflow_rules_open(struct inode *inode, struct file *file)
+{
+ return seq_open(file, &infoflow_rules_seqops);
+}
+
+static const struct file_operations infoflow_rules_ops = {
+ .open = infoflow_rules_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = seq_release,
+};
+
+int infoflow_policy_show(struct seq_file *m, void *v)
+{
+ struct infoflow_ctx *ctx = v;
+ struct infoflow_subj_desc *desc;
+
+ if (ctx->flags & CTX_FLAG_FILTER)
+ seq_printf(m, "filter");
+ else if (ctx->flags & CTX_FLAG_TCB)
+ seq_printf(m, "tcb");
+ else
+ return 0;
+
+ if (ctx->class == CLASS_PROCESS)
+ seq_printf(m, " subj=");
+ else
+ seq_printf(m, " obj=");
+
+ seq_printf(m, "%s:%s", ctx->label,
+ infoflow_class_array[ctx->class].name);
+
+ list_for_each_entry(desc, &ctx->filter_subjs, list)
+ seq_printf(m, " subj=%s:%s", desc->ctx->label,
+ infoflow_class_array[desc->ctx->class].name);
+
+ seq_printf(m, "\n");
+
+ return 0;
+}
+
+static const struct seq_operations policy_seq_ops = {
+ .start = infoflow_ctx_start,
+ .next = infoflow_ctx_next,
+ .stop = infoflow_ctx_stop,
+ .show = infoflow_policy_show,
+};
+
+enum {
+ Opt_err = -1, Opt_tcb = 1, Opt_filter, Opt_subj, Opt_obj,
+};
+
+static match_table_t policy_tokens = {
+ {Opt_tcb, "tcb"},
+ {Opt_filter, "filter"},
+ {Opt_subj, "subj=%s"},
+ {Opt_obj, "obj=%s"},
+};
+
+int valid_policy;
+
+static int infoflow_open_policy(struct inode *inode, struct file *file)
+{
+ if ((infoflow_init_flags & INFOFLOW_POLICY_INIT) &&
+ (file->f_mode & FMODE_WRITE))
+ return -EPERM;
+
+ valid_policy = 1;
+
+ return seq_open(file, &policy_seq_ops);
+}
+
+static struct infoflow_ctx *infoflow_add_ctx(char *label, u8 flags, bool obj)
+{
+ enum infoflow_class class = CLASS_PROCESS;
+ char *class_ptr;
+
+ if (!obj)
+ goto out;
+
+ class_ptr = strrchr(label, ':');
+ if (!class_ptr)
+ return ERR_PTR(-EINVAL);
+
+ *class_ptr++ = '\0';
+
+ class = infoflow_lookup_class(class_ptr);
+ if (class == CLASS__LAST) {
+ pr_err("Invalid class %s\n", class_ptr);
+ return ERR_PTR(-EINVAL);
+ }
+out:
+ return infoflow_ctx_insert_label(class, label, strlen(label), flags);
+}
+
+static int infoflow_parse_rule(char *rule)
+{
+ struct infoflow_ctx *subj = NULL, *obj = NULL;
+ LIST_HEAD(filtering_subjects);
+ struct infoflow_subj_desc *desc, *tmp_desc;
+ char *p;
+ u8 flags = 0;
+ int result = 0;
+
+ while ((p = strsep(&rule, " \t")) != NULL) {
+ substring_t args[MAX_OPT_ARGS];
+ int token;
+
+ if (result < 0)
+ break;
+ if ((*p == '\0') || (*p == ' ') || (*p == '\t'))
+ continue;
+ token = match_token(p, policy_tokens, args);
+ switch (token) {
+ case Opt_tcb:
+ if (flags) {
+ pr_err("Rule type already specified\n");
+ result = -EINVAL;
+ break;
+ }
+
+ flags |= CTX_FLAG_TCB;
+ break;
+ case Opt_filter:
+ if (flags) {
+ pr_err("Rule type already specified\n");
+ result = -EINVAL;
+ break;
+ }
+
+ flags |= CTX_FLAG_FILTER;
+ break;
+ case Opt_subj:
+ if (!flags) {
+ pr_err("Rule type not specified\n");
+ result = -EINVAL;
+ break;
+ }
+
+ if (!(flags & CTX_FLAG_FILTER) && subj) {
+ pr_err("Subject already specified\n");
+ result = -EINVAL;
+ break;
+ }
+
+ if ((flags & CTX_FLAG_FILTER) && !obj) {
+ pr_err("Object not specified\n");
+ result = -EINVAL;
+ break;
+ }
+
+ subj = infoflow_add_ctx(args[0].from,
+ (flags & CTX_FLAG_FILTER) ?
+ 0 : flags, false);
+ if (IS_ERR(subj)) {
+ result = PTR_ERR(subj);
+ break;
+ }
+
+ if (flags & CTX_FLAG_FILTER) {
+ result = infoflow_ctx_find_add_subj(0, 0, &subj,
+ 0, 0, 0, &obj, 0, 0, 0, NULL,
+ TYPE_FILTER);
+ if (result < 0)
+ break;
+ }
+
+ desc = kmalloc(sizeof(*desc), GFP_KERNEL);
+ if (!desc) {
+ result = -ENOMEM;
+ break;
+ }
+ desc->ctx = subj;
+ list_add_tail(&desc->list, &filtering_subjects);
+ break;
+ case Opt_obj:
+ if (!flags || obj) {
+ pr_err("Rule type not specified or "
+ "object specified\n");
+ result = -EINVAL;
+ break;
+ }
+
+ obj = infoflow_add_ctx(args[0].from, flags, true);
+ if (IS_ERR(obj)) {
+ result = -ENOMEM;
+ break;
+ }
+ break;
+ case Opt_err:
+ result = -EINVAL;
+ break;
+ }
+ }
+
+ if (!result && !flags) {
+ pr_err("Rule type not specified\n");
+ result = -EINVAL;
+ }
+
+ list_for_each_entry_safe(desc, tmp_desc, &filtering_subjects, list) {
+ if (!(desc->ctx->flags & CTX_FLAG_TCB)) {
+ pr_err("Filtering subject %s not added to TCB\n",
+ desc->ctx->label);
+ list_del(&desc->list);
+ kfree(desc);
+ result = -EINVAL;
+ }
+ }
+
+ if (result < 0)
+ infoflow_ctx_delete();
+
+ return result;
+}
+
+static ssize_t infoflow_write_policy(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ char *data, *data_ptr, *newline_ptr;
+ int rc = 0;
+
+ if (cap_capable(current_cred(), &init_user_ns, CAP_MAC_ADMIN,
+ CAP_OPT_NONE))
+ return -EACCES;
+
+ if (*ppos != 0)
+ return -EINVAL;
+
+ if (count >= PAGE_SIZE)
+ count = PAGE_SIZE - 1;
+
+ data_ptr = data = memdup_user_nul(buf, count);
+ if (IS_ERR(data))
+ return PTR_ERR(data);
+
+ while (*(newline_ptr = strchrnul(data_ptr, '\n')) == '\n') {
+ *newline_ptr = '\0';
+
+ rc = infoflow_parse_rule(data_ptr);
+ if (rc < 0)
+ break;
+
+ data_ptr = newline_ptr + 1;
+ }
+
+ kfree(data);
+
+ if (rc < 0) {
+ valid_policy = 0;
+ return rc;
+ }
+
+ return data_ptr - data;
+}
+
+static int infoflow_release_policy(struct inode *inode, struct file *file)
+{
+ if (!(file->f_mode & FMODE_WRITE))
+ return 0;
+
+ if (!valid_policy) {
+ pr_err("Cannot load infoflow policy\n");
+ return 0;
+ }
+
+ infoflow_init_flags |= INFOFLOW_POLICY_INIT;
+
+ pr_info("Successfully loaded Infoflow LSM policy\n");
+
+ return 0;
+}
+
+static const struct file_operations infoflow_policy_ops = {
+ .open = infoflow_open_policy,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .write = infoflow_write_policy,
+ .release = infoflow_release_policy,
+};
+
+
+static int infoflow_open_mode(struct inode *inode, struct file *file)
+{
+ if ((file->f_mode & FMODE_WRITE) &&
+ cap_capable(current_cred(), &init_user_ns, CAP_MAC_ADMIN,
+ CAP_OPT_NONE))
+ return -EACCES;
+
+ return 0;
+}
+
+#define TMPBUFLEN 32
+static ssize_t infoflow_read_mode(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ char tmpbuf[TMPBUFLEN];
+ ssize_t length;
+
+ length = scnprintf(tmpbuf, sizeof(tmpbuf), "%s",
+ infoflow_modes_str[infoflow_mode]);
+ return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
+}
+
+static ssize_t infoflow_write_mode(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+
+{
+ char *new_mode = NULL;
+ ssize_t length;
+ int i;
+
+ if (count >= PAGE_SIZE)
+ return -ENOMEM;
+
+ /* No partial writes. */
+ if (*ppos != 0)
+ return -EINVAL;
+
+ new_mode = memdup_user_nul(buf, count);
+ if (IS_ERR(new_mode))
+ return PTR_ERR(new_mode);
+
+ for (i = 0; i < INFOFLOW_MODE__LAST; i++) {
+ if (!strcmp(new_mode, infoflow_modes_str[i])) {
+ infoflow_mode = i;
+ break;
+ }
+ }
+
+ if (i == INFOFLOW_MODE__LAST) {
+ length = -EINVAL;
+ goto out;
+ }
+
+ length = count;
+out:
+ kfree(new_mode);
+ return length;
+}
+
+static const struct file_operations infoflow_enforce_ops = {
+ .open = infoflow_open_mode,
+ .read = infoflow_read_mode,
+ .write = infoflow_write_mode,
+ .llseek = generic_file_llseek,
+};
+
+static int __init init_infoflow_fs(void)
+{
+ infoflow_dir = securityfs_create_dir("infoflow", NULL);
+ if (IS_ERR(infoflow_dir))
+ goto out;
+
+ infoflow_rules = securityfs_create_file("rules", S_IRUGO,
+ infoflow_dir, NULL, &infoflow_rules_ops);
+ if (IS_ERR(infoflow_rules))
+ goto out;
+
+ infoflow_policy = securityfs_create_file("policy", S_IRUGO | S_IWUSR,
+ infoflow_dir, NULL, &infoflow_policy_ops);
+ if (IS_ERR(infoflow_policy))
+ goto out;
+
+ infoflow_enforce = securityfs_create_file("enforce",
+ S_IRUGO | S_IWUSR, infoflow_dir, NULL,
+ &infoflow_enforce_ops);
+ if (IS_ERR(infoflow_enforce))
+ goto out;
+
+ return 0;
+out:
+ securityfs_remove(infoflow_enforce);
+ securityfs_remove(infoflow_policy);
+ securityfs_remove(infoflow_rules);
+ securityfs_remove(infoflow_dir);
+ return -1;
+}
+
+__initcall(init_infoflow_fs);
diff --git a/security/infoflow/infoflow_lsm.c b/security/infoflow/infoflow_lsm.c
new file mode 100644
index 000000000000..0737b8e07c47
--- /dev/null
+++ b/security/infoflow/infoflow_lsm.c
@@ -0,0 +1,778 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2018,2019 Huawei Technologies Duesseldorf GmbH
+ *
+ * Author: Roberto Sassu <roberto.sassu at huawei.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ * File: infoflow_lsm.c
+ * Function implementing LSM hooks.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/cred.h>
+#include <linux/magic.h>
+#include <linux/audit.h>
+#include <linux/xattr.h>
+
+#include "infoflow.h"
+
+struct infoflow_class_desc infoflow_class_array[CLASS__LAST] = {
+ [CLASS_LNK_FILE] = { .name = "lnk_file", .excluded = 1 },
+ [CLASS_REG_FILE] = { .name = "file" },
+ [CLASS_DIR] = { .name = "dir", .excluded = 1 },
+ [CLASS_CHR_FILE] = { .name = "chr_file" },
+ [CLASS_BLK_FILE] = { .name = "blk_file" },
+ [CLASS_FIFO_FILE] = { .name = "fifo_file" },
+ [CLASS_SOCK_FILE] = { .name = "sock_file", .excluded = 1 },
+ [CLASS_IPC] = { .name = "ipc" },
+ [CLASS_MSGQ] = { .name = "msgq" },
+ [CLASS_SHM] = { .name = "shm" },
+ [CLASS_SEM] = { .name = "sem" },
+ [CLASS_SOCKET] = { .name = "socket" },
+ [CLASS_KEY] = { .name = "key" },
+ [CLASS_PROCESS] = { .name = "process" },
+ [CLASS_KERNEL] = { .name = "kernel" },
+ [CLASS_MODULE] = { .name = "module" },
+ [CLASS_UNDEFINED] = { .name = "undefined" },
+};
+
+const char *infoflow_modes_str[INFOFLOW_MODE__LAST] = {
+ [INFOFLOW_DISABLED] = "disabled",
+ [INFOFLOW_DISCOVER] = "discover",
+ [INFOFLOW_ENFORCE] = "enforce",
+ [INFOFLOW_ENFORCE_AUDIT] = "enforce-audit",
+ [INFOFLOW_PERMISSIVE] = "permissive",
+ [INFOFLOW_PERMISSIVE_AUDIT] = "permissive-audit",
+};
+
+int infoflow_mode __lsm_ro_after_init = INFOFLOW_ENFORCE;
+static int __init infoflow_mode_setup(char *str)
+{
+ int i;
+
+ for (i = 0; i < INFOFLOW_MODE__LAST; i++) {
+ if (!strcmp(str, infoflow_modes_str[i])) {
+ infoflow_mode = i;
+ break;
+ }
+ }
+
+ if (i == INFOFLOW_MODE__LAST)
+ pr_err("Unknown mode %s\n", str);
+
+ return 1;
+}
+__setup("infoflow_mode=", infoflow_mode_setup);
+
+int infoflow_enabled __lsm_ro_after_init = 1;
+static int __init infoflow_enabled_setup(char *str)
+{
+ unsigned long enabled;
+
+ if (!kstrtoul(str, 0, &enabled))
+ infoflow_enabled = enabled ? 1 : 0;
+ return 1;
+}
+__setup("infoflow=", infoflow_enabled_setup);
+
+int infoflow_init_flags;
+static int __init infoflow_promote_setup(char *str)
+{
+ infoflow_init_flags |= INFOFLOW_PROMOTE;
+ return 1;
+}
+__setup("infoflow_promote", infoflow_promote_setup);
+
+static int infoflow_seqno;
+
+static int infoflow_security_change(struct notifier_block *nb,
+ unsigned long event, void *lsm_data)
+{
+ if (event != LSM_POLICY_CHANGE)
+ return NOTIFY_DONE;
+
+ infoflow_seqno++;
+ infoflow_ctx_update_sid();
+ infoflow_init_flags |= INFOFLOW_PARENT_LSM_INIT;
+
+ return NOTIFY_OK;
+}
+
+static struct notifier_block infoflow_lsm_nb = {
+ .notifier_call = infoflow_security_change,
+};
+
+static int infoflow_inode_init_security(struct inode *inode, struct inode *dir,
+ const struct qstr *qstr,
+ const char **name, void **value,
+ size_t *len)
+{
+ const struct cred *cred = current_cred();
+ u8 *iflags = infoflow_inode(inode);
+ u16 class = infoflow_inode_class(inode->i_mode);
+ struct infoflow_ctx *sctx;
+ u8 flags = CTX_FLAG_TCB;
+ u32 sid;
+
+ if (name)
+ *name = XATTR_INFOFLOW_SUFFIX;
+
+ if (infoflow_class_array[class].excluded)
+ return 0;
+
+ if (!(infoflow_init_flags & INFOFLOW_POLICY_INIT))
+ return -EOPNOTSUPP;
+
+ validate_creds(cred);
+
+ security_cred_getsecid(cred, &sid);
+ sctx = infoflow_ctx_find_sid(CLASS_PROCESS, sid);
+
+ if (!sctx || !(sctx->flags & CTX_FLAG_TCB))
+ return 0;
+
+ *iflags |= (flags | CTX_FLAG_INITIALIZED);
+
+ if (value && len) {
+ *value = kmemdup(&flags, sizeof(flags), GFP_NOFS);
+ if (!*value)
+ return -ENOMEM;
+
+ *len = sizeof(flags);
+ }
+
+ return 0;
+}
+
+int infoflow_check_fs(struct inode *inode)
+{
+ struct super_block *sb = inode->i_sb;
+
+ switch (sb->s_magic) {
+ case PROC_SUPER_MAGIC:
+ case SYSFS_MAGIC:
+ case DEBUGFS_MAGIC:
+ case RAMFS_MAGIC:
+ case DEVPTS_SUPER_MAGIC:
+ case BINFMTFS_MAGIC:
+ case SECURITYFS_MAGIC:
+ case SELINUX_MAGIC:
+ case SMACK_MAGIC:
+ case NSFS_MAGIC:
+ case CGROUP_SUPER_MAGIC:
+ case CGROUP2_SUPER_MAGIC:
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+static int infoflow_inode_init(struct dentry *opt_dentry, struct inode *inode,
+ bool may_sleep)
+{
+ u8 *iflags = infoflow_inode(inode);
+ u16 class = infoflow_inode_class(inode->i_mode);
+ struct dentry *dentry = NULL;
+ u8 flags = 0;
+ int rc;
+
+ might_sleep_if(may_sleep);
+
+ if (infoflow_class_array[class].excluded)
+ return 0;
+
+ if (!(infoflow_init_flags & INFOFLOW_POLICY_INIT))
+ return 0;
+
+ if (!may_sleep)
+ return -ECHILD;
+
+ if (*iflags & CTX_FLAG_INITIALIZED)
+ return 0;
+
+ if (!infoflow_check_fs(inode) || !(inode->i_opflags & IOP_XATTR)) {
+ *iflags |= CTX_FLAG_INITIALIZED;
+ return 0;
+ }
+
+ if (!opt_dentry) {
+ dentry = d_find_alias(inode);
+ if (!dentry)
+ dentry = d_find_any_alias(inode);
+ } else {
+ dentry = dget(opt_dentry);
+ }
+
+ if (!dentry)
+ return 0;
+
+ rc = __vfs_getxattr(dentry, inode, XATTR_NAME_INFOFLOW, &flags,
+ sizeof(flags));
+ if (rc == sizeof(flags))
+ *iflags |= (flags & CTX_FLAG_TCB);
+
+ *iflags |= CTX_FLAG_INITIALIZED;
+
+ if (dentry)
+ dput(dentry);
+
+ return 0;
+}
+
+static void infoflow_d_instantiate(struct dentry *opt_dentry,
+ struct inode *inode)
+{
+ infoflow_inode_init(opt_dentry, inode, true);
+}
+
+static int inode_has_perm(const struct cred *cred,
+ struct inode *inode,
+ int mask,
+ struct common_audit_data *adp)
+{
+ u8 *iflags = infoflow_inode(inode);
+ u32 ssid, isid;
+
+ if (!infoflow_check_fs(inode))
+ return 0;
+
+ validate_creds(cred);
+
+ if (unlikely(IS_PRIVATE(inode)))
+ return 0;
+
+ security_cred_getsecid(cred, &ssid);
+ security_inode_getsecid(inode, &isid);
+
+ return infoflow_allow_access(CLASS_PROCESS, ssid,
+ infoflow_inode_class(inode->i_mode), isid,
+ iflags, mask, adp);
+}
+
+static int infoflow_inode_permission(struct inode *inode, int mask)
+{
+ const struct cred *cred = current_cred();
+ struct common_audit_data ad;
+
+ mask &= (MAY_READ|MAY_WRITE|MAY_EXEC|MAY_APPEND);
+
+ /* No permission to check. Existence test. */
+ if (!mask)
+ return 0;
+
+ ad.type = LSM_AUDIT_DATA_INODE;
+ ad.u.inode = inode;
+
+ return inode_has_perm(cred, inode, mask, &ad);
+}
+
+static int infoflow_inode_setxattr(struct dentry *dentry, const char *name,
+ const void *value, size_t size, int flags)
+{
+ struct inode *inode = d_backing_inode(dentry);
+ const struct cred *cred = current_cred();
+
+ if (strcmp(name, XATTR_NAME_INFOFLOW))
+ return 0;
+
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return inode_has_perm(cred, inode, MAY_WRITE, NULL);
+}
+
+static void infoflow_inode_post_setxattr(struct dentry *dentry,
+ const char *name, const void *value,
+ size_t size, int flags)
+{
+ struct inode *inode = d_backing_inode(dentry);
+ u8 *iflags = infoflow_inode(inode);
+
+ if (strcmp(name, XATTR_NAME_INFOFLOW) != 0)
+ return;
+
+ if (size != 1)
+ return;
+
+ *iflags = *(u8 *)value;
+}
+
+static int infoflow_inode_removexattr(struct dentry *dentry, const char *name)
+{
+ if (strcmp(name, XATTR_NAME_INFOFLOW))
+ return 0;
+
+ /* There is no inode_post_removexattr() hook, we cannot clear
+ * the inode flags.
+ */
+ return -EACCES;
+}
+
+static int infoflow_inode_setsecurity(struct inode *inode, const char *name,
+ const void *value, size_t size, int flags)
+{
+ u8 *iflags = infoflow_inode(inode);
+
+ if (strcmp(name, XATTR_INFOFLOW_SUFFIX))
+ return -EOPNOTSUPP;
+
+ if (!value || !size)
+ return -EACCES;
+
+ if (size != 1)
+ return -EINVAL;
+
+ *iflags = *(u8 *)value;
+
+ return 0;
+}
+
+static int file_has_perm(const struct cred *cred,
+ struct file *file,
+ int mask)
+{
+ struct inode *inode = file_inode(file);
+ struct common_audit_data ad;
+ int rc;
+
+ ad.type = LSM_AUDIT_DATA_FILE;
+ ad.u.file = file;
+
+ /* av is zero if only checking access to the descriptor. */
+ rc = 0;
+ if (mask)
+ rc = inode_has_perm(cred, inode, mask, &ad);
+
+ return rc;
+}
+
+/* Same as path_has_perm, but uses the inode from the file struct. */
+static inline int file_path_has_perm(const struct cred *cred,
+ struct file *file,
+ int mask)
+{
+ struct common_audit_data ad;
+
+ ad.type = LSM_AUDIT_DATA_FILE;
+ ad.u.file = file;
+
+ return inode_has_perm(cred, file_inode(file), mask, &ad);
+}
+
+static int infoflow_revalidate_file_permission(struct file *file, int mask)
+{
+ const struct cred *cred = current_cred();
+
+ return file_has_perm(cred, file, mask);
+}
+
+static int infoflow_file_permission(struct file *file, int mask)
+{
+ const struct cred *cred = current_cred();
+ struct inode *inode = file_inode(file);
+ struct file_security_struct *fsec = infoflow_file(file);
+ u32 ssid, isid;
+
+ if (!mask)
+ /* No permission to check. Existence test. */
+ return 0;
+
+ validate_creds(cred);
+ security_cred_getsecid(cred, &ssid);
+
+ security_inode_getsecid(inode, &isid);
+
+ if (ssid == fsec->sid && fsec->isid == isid &&
+ fsec->pseqno == infoflow_seqno)
+ /* No change since file_open check. */
+ return 0;
+
+ return infoflow_revalidate_file_permission(file, mask);
+}
+
+static int infoflow_file_receive(struct file *file)
+{
+ const struct cred *cred = current_cred();
+
+ return file_has_perm(cred, file, infoflow_file_f_mode_to_mask(file));
+}
+
+static int infoflow_file_open(struct file *file)
+{
+ struct file_security_struct *fsec;
+ u32 isid;
+
+ fsec = infoflow_file(file);
+
+ security_inode_getsecid(file_inode(file), &isid);
+
+ fsec->isid = isid;
+ fsec->pseqno = infoflow_seqno;
+
+ return file_path_has_perm(file->f_cred, file,
+ infoflow_file_f_mode_to_mask(file));
+}
+
+static int infoflow_kernel_module_from_file(struct file *file, bool module)
+{
+ struct common_audit_data ad;
+ const struct cred *cred = current_cred();
+ enum infoflow_class class = CLASS_MODULE;
+ struct inode *inode;
+ u32 ssid, isid;
+ u8 *iflags;
+
+ validate_creds(cred);
+ security_cred_getsecid(cred, &ssid);
+
+ /* init_module */
+ if (file == NULL) {
+ if (!module)
+ class = CLASS_UNDEFINED;
+
+ return infoflow_allow_access(CLASS_KERNEL, ssid, class, ssid,
+ NULL, MAY_READ, NULL);
+ }
+
+ /* finit_module */
+ ad.type = LSM_AUDIT_DATA_FILE;
+ ad.u.file = file;
+
+ inode = file_inode(file);
+ security_inode_getsecid(inode, &isid);
+ iflags = infoflow_inode(inode);
+
+ if (!module)
+ class = infoflow_inode_class(inode->i_mode);
+
+ return infoflow_allow_access(CLASS_KERNEL, ssid, class, isid, iflags,
+ MAY_READ, &ad);
+}
+
+static int infoflow_kernel_read_file(struct file *file,
+ enum kernel_read_file_id id)
+{
+ return infoflow_kernel_module_from_file(file, id == READING_MODULE);
+}
+
+static int infoflow_kernel_load_data(enum kernel_load_data_id id)
+{
+ return infoflow_kernel_module_from_file(NULL, id == LOADING_MODULE);
+}
+
+static int infoflow_setprocattr(const char *name, void *value, size_t size)
+{
+ const struct cred *cred = current_cred();
+ struct infoflow_ctx *old_ctx, *new_ctx;
+ u32 old_ssid, new_ssid;
+ int rc;
+
+ if (strcmp(name, "current"))
+ return 0;
+
+ validate_creds(cred);
+ security_cred_getsecid(cred, &old_ssid);
+
+ old_ctx = infoflow_ctx_find_sid(CLASS_PROCESS, old_ssid);
+
+ rc = security_secctx_to_secid((char *)value, size, &new_ssid);
+ if (rc < 0)
+ return -EPERM;
+
+ new_ctx = infoflow_ctx_find_sid(CLASS_PROCESS, new_ssid);
+
+ /* We cannot let a non-TCB process go inside the TCB, because
+ * load-time integrity of non-TCB processes cannot be determined.
+ */
+ if ((!old_ctx || !(old_ctx->flags & CTX_FLAG_TCB)) &&
+ new_ctx && (new_ctx->flags & CTX_FLAG_TCB))
+ return -EPERM;
+
+ return 0;
+}
+
+static int ipc_has_perm(const struct cred *cred, u32 sid,
+ enum infoflow_class oclass,
+ struct kern_ipc_perm *ipc_perms, int mask)
+{
+ struct common_audit_data ad;
+ u32 ssid, isid;
+
+ if (cred) {
+ validate_creds(cred);
+ security_cred_getsecid(cred, &ssid);
+ } else {
+ ssid = sid;
+ }
+
+ security_ipc_getsecid(ipc_perms, &isid);
+
+ ad.type = LSM_AUDIT_DATA_IPC;
+ ad.u.ipc_id = ipc_perms->key;
+
+ return infoflow_allow_access(CLASS_PROCESS, ssid, oclass, isid, NULL,
+ mask, &ad);
+}
+
+static int infoflow_ipc_permission(struct kern_ipc_perm *ipcp, short flag)
+{
+ const struct cred *cred = current_cred();
+ int mask = 0;
+
+ if (flag & S_IRUGO)
+ mask |= MAY_READ;
+ if (flag & S_IWUGO)
+ mask |= MAY_WRITE;
+
+ if (mask == 0)
+ return 0;
+
+ return ipc_has_perm(cred, 0, CLASS_IPC, ipcp, mask);
+}
+
+static int infoflow_msg_queue_msgsnd(struct kern_ipc_perm *msq,
+ struct msg_msg *msg, int msqflg)
+{
+ const struct cred *cred = current_cred();
+
+ return ipc_has_perm(cred, 0, CLASS_MSGQ, msq, MAY_WRITE);
+}
+
+static int infoflow_msg_queue_msgrcv(struct kern_ipc_perm *msq,
+ struct msg_msg *msg,
+ struct task_struct *target,
+ long type, int mode)
+{
+ u32 ssid;
+
+ security_task_getsecid(target, &ssid);
+
+ return ipc_has_perm(NULL, ssid, CLASS_MSGQ, msq, MAY_READ);
+}
+
+static int infoflow_shm_shmat(struct kern_ipc_perm *shp,
+ char __user *shmaddr, int shmflg)
+{
+ const struct cred *cred = current_cred();
+ int mask;
+
+ if (shmflg & SHM_RDONLY)
+ mask = MAY_READ;
+ else
+ mask = MAY_READ | MAY_WRITE;
+
+ return ipc_has_perm(cred, 0, CLASS_SHM, shp, mask);
+}
+
+/* Note, at this point, sma is locked down */
+static int infoflow_sem_semctl(struct kern_ipc_perm *sma, int cmd)
+{
+ const struct cred *cred = current_cred();
+ int mask;
+
+ switch (cmd) {
+ case GETVAL:
+ case GETALL:
+ mask = MAY_READ;
+ break;
+ case SETVAL:
+ case SETALL:
+ mask = MAY_WRITE;
+ break;
+ default:
+ return 0;
+ }
+
+ return ipc_has_perm(cred, 0, CLASS_SEM, sma, mask);
+}
+
+static int infoflow_sem_semop(struct kern_ipc_perm *sma,
+ struct sembuf *sops, unsigned nsops, int alter)
+{
+ const struct cred *cred = current_cred();
+ int mask;
+
+ if (alter)
+ mask = MAY_READ | MAY_WRITE;
+ else
+ mask = MAY_READ;
+
+ return ipc_has_perm(cred, 0, CLASS_SEM, sma, mask);
+}
+
+static int infoflow_socket_unix_may_send(struct socket *sock,
+ struct socket *other)
+{
+ struct common_audit_data ad;
+ struct lsm_network_audit net = {0,};
+ struct flowi fl_sock, fl_other;
+
+ ad.type = LSM_AUDIT_DATA_NET;
+ ad.u.net = &net;
+ ad.u.net->sk = other->sk;
+
+ security_sk_classify_flow(sock->sk, &fl_sock);
+ security_sk_classify_flow(other->sk, &fl_other);
+
+ return infoflow_allow_access(CLASS_PROCESS, fl_sock.flowi_secid,
+ CLASS_SOCKET, fl_other.flowi_secid,
+ NULL, MAY_WRITE, &ad);
+}
+
+static int sock_has_perm(struct sock *sk, int mask)
+{
+ const struct cred *cred = current_cred();
+ struct common_audit_data ad;
+ struct lsm_network_audit net = {0,};
+ struct flowi fl_sk;
+ u32 ssid;
+
+ ad.type = LSM_AUDIT_DATA_NET;
+ ad.u.net = &net;
+ ad.u.net->sk = sk;
+
+ validate_creds(cred);
+ security_cred_getsecid(cred, &ssid);
+
+ security_sk_classify_flow(sk, &fl_sk);
+
+ return infoflow_allow_access(CLASS_PROCESS, ssid, CLASS_SOCKET,
+ fl_sk.flowi_secid, NULL, mask, &ad);
+}
+
+static int infoflow_socket_sendmsg(struct socket *sock, struct msghdr *msg,
+ int size)
+{
+ return sock_has_perm(sock->sk, MAY_WRITE);
+}
+
+static int infoflow_socket_recvmsg(struct socket *sock, struct msghdr *msg,
+ int size, int flags)
+{
+ return sock_has_perm(sock->sk, MAY_READ);
+}
+
+static int infoflow_audit_rule_init(u32 field, u32 op, char *rulestr,
+ void **vrule)
+{
+ if (field != AUDIT_SUBJ_USER)
+ return -EINVAL;
+
+ if (op != Audit_equal && op != Audit_not_equal)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int infoflow_audit_rule_known(struct audit_krule *krule)
+{
+ struct audit_field *f;
+ int i;
+
+ for (i = 0; i < krule->field_count; i++) {
+ f = &krule->fields[i];
+
+ if (f->type == AUDIT_SUBJ_USER)
+ return 1;
+ }
+
+ return 0;
+}
+
+static int infoflow_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule)
+{
+ struct infoflow_ctx *ctx;
+
+ if (field != AUDIT_SUBJ_USER)
+ return 0;
+
+ if (!(infoflow_init_flags & INFOFLOW_PARENT_LSM_INIT))
+ return (op == Audit_equal) ? true : false;
+
+ ctx = infoflow_ctx_find_sid(CLASS_PROCESS, sid);
+
+ if (op == Audit_equal)
+ return (ctx && (ctx->flags & CTX_FLAG_TCB));
+ if (op == Audit_not_equal)
+ return (!ctx || !(ctx->flags & CTX_FLAG_TCB));
+
+ return 0;
+}
+
+struct lsm_blob_sizes infoflow_blob_sizes = {
+ .lbs_inode = sizeof(u8),
+ .lbs_file = sizeof(struct file_security_struct),
+};
+
+static struct security_hook_list infoflow_hooks[] __lsm_ro_after_init = {
+ LSM_HOOK_INIT(inode_init_security, infoflow_inode_init_security),
+
+ LSM_HOOK_INIT(d_instantiate, infoflow_d_instantiate),
+
+ LSM_HOOK_INIT(inode_permission, infoflow_inode_permission),
+ LSM_HOOK_INIT(inode_setxattr, infoflow_inode_setxattr),
+ LSM_HOOK_INIT(inode_post_setxattr, infoflow_inode_post_setxattr),
+ LSM_HOOK_INIT(inode_removexattr, infoflow_inode_removexattr),
+ LSM_HOOK_INIT(inode_setsecurity, infoflow_inode_setsecurity),
+
+ LSM_HOOK_INIT(file_permission, infoflow_file_permission),
+ LSM_HOOK_INIT(file_receive, infoflow_file_receive),
+ LSM_HOOK_INIT(file_open, infoflow_file_open),
+
+ LSM_HOOK_INIT(kernel_read_file, infoflow_kernel_read_file),
+ LSM_HOOK_INIT(kernel_load_data, infoflow_kernel_load_data),
+
+ LSM_HOOK_INIT(setprocattr, infoflow_setprocattr),
+
+ LSM_HOOK_INIT(ipc_permission, infoflow_ipc_permission),
+
+ LSM_HOOK_INIT(msg_queue_msgsnd, infoflow_msg_queue_msgsnd),
+ LSM_HOOK_INIT(msg_queue_msgrcv, infoflow_msg_queue_msgrcv),
+
+ LSM_HOOK_INIT(shm_shmat, infoflow_shm_shmat),
+
+ LSM_HOOK_INIT(sem_semctl, infoflow_sem_semctl),
+ LSM_HOOK_INIT(sem_semop, infoflow_sem_semop),
+
+ LSM_HOOK_INIT(unix_may_send, infoflow_socket_unix_may_send),
+ LSM_HOOK_INIT(socket_sendmsg, infoflow_socket_sendmsg),
+ LSM_HOOK_INIT(socket_recvmsg, infoflow_socket_recvmsg),
+
+#ifdef CONFIG_AUDIT
+ LSM_HOOK_INIT(audit_rule_init, infoflow_audit_rule_init),
+ LSM_HOOK_INIT(audit_rule_known, infoflow_audit_rule_known),
+ LSM_HOOK_INIT(audit_rule_match, infoflow_audit_rule_match),
+#endif
+
+};
+
+static __init int infoflow_init(void)
+{
+ int rc;
+
+ if (!infoflow_enabled)
+ return 0;
+
+ rc = register_blocking_lsm_notifier(&infoflow_lsm_nb);
+ if (rc) {
+ pr_warn("Couldn't register LSM notifier. rc %d\n", rc);
+ return rc;
+ }
+
+ security_add_hooks(infoflow_hooks, ARRAY_SIZE(infoflow_hooks),
+ "infoflow");
+ return 0;
+}
+
+DEFINE_LSM(infoflow) = {
+ .name = "infoflow",
+ .flags = LSM_FLAG_LEGACY_MAJOR,
+ .blobs = &infoflow_blob_sizes,
+ .init = infoflow_init,
+};
diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c
index fbe0bb5cd7c4..0a54e488409c 100644
--- a/security/integrity/evm/evm_main.c
+++ b/security/integrity/evm/evm_main.c
@@ -51,6 +51,7 @@ static struct xattr_list evm_config_default_xattrnames[] = {
#ifdef CONFIG_IMA_APPRAISE
{.name = XATTR_NAME_IMA},
#endif
+ {.name = XATTR_NAME_INFOFLOW},
{.name = XATTR_NAME_CAPS},
};
--
2.17.1
More information about the Linux-security-module-archive
mailing list