Репозиторий Sisyphus
Последнее обновление: 1 октября 2023 | Пакетов: 18631 | Посещений: 37419523
en ru br
Репозитории ALT
S:6.1.55-alt1
5.1: 2.6.30-alt15
4.1: 2.6.25-alt8.M41.5
www.altlinux.org/Changes

Группа :: Система/Ядро и оборудование
Пакет: kernel-image-std-def

 Главная   Изменения   Спек   Патчи   Sources   Загрузить   Gear   Bugs and FR  Repocop 

Патч: altha.patch
Скачать


 Documentation/admin-guide/LSM/AltHa.rst | 101 +++++++++
 Documentation/admin-guide/LSM/index.rst |   1 +
 security/Kconfig                        |   4 +-
 security/Makefile                       |   2 +
 security/altha/Kconfig                  |  11 +
 security/altha/Makefile                 |   3 +
 security/altha/altha_lsm.c              | 351 ++++++++++++++++++++++++++++++++
 security/kiosk/Kconfig                  |   9 +
 security/kiosk/Makefile                 |   3 +
 security/kiosk/kiosk-test.sh            | 252 +++++++++++++++++++++++
 security/kiosk/kiosk_lsm.c              | 337 ++++++++++++++++++++++++++++++
 11 files changed, 1073 insertions(+), 1 deletion(-)
diff --git a/Documentation/admin-guide/LSM/AltHa.rst b/Documentation/admin-guide/LSM/AltHa.rst
new file mode 100644
index 000000000000..be698709d3f0
--- /dev/null
+++ b/Documentation/admin-guide/LSM/AltHa.rst
@@ -0,0 +1,101 @@
+====
+AltHa
+====
+
+AltHa is a Linux Security Module currently has three userspace hardening options:
+    * ignore SUID on binaries (with exceptions possible);
+    * prevent running selected script interpreters in interactive mode;
+    * disable open file unlinking in selected dirs.
+    * enable kiosk mode
+
+
+It is selectable at build-time with ``CONFIG_SECURITY_ALTHA``, and should be
+enabled in runtime by command line option ``altha=1`` and configured
+through sysctls in ``/proc/sys/kernel/altha``.
+
+NoSUID
+============
+Modern Linux systems can be used with minimal (or even zero at least for OWL and ALT) usage of SUID programms, but in many cases in full-featured desktop or server systems there are plenty of them: uncounted and sometimes unnecessary. Privileged programms are always an attack surface, but mounting filesystems with ``nosuid`` flag doesn't provide enough granularity in SUID binaries management. This LSM module provides a single control point for all SUID binaries. When this submodule is enabled, SUID bits on all binaries except explicitly listed are system-wide ignored.
+
+Sysctl parameters and defaults:
+
+* ``kernel.altha.nosuid.enabled = 0``, set to 1 to enable
+* ``kernel.altha.nosuid.exceptions =``, colon-separated list of enabled SUID binaries, for example: ``/bin/su:/usr/libexec/hasher-priv/hasher-priv``
+
+RestrScript
+============
+There is a one way to hardening: prevent users from executing ther own arbitrary code. Traditionally it can be done setting on user-writable filesystems ``noexec`` flag. But modern script languages such as Python also can be used to write exploits or even load arbitary machine code via ``dlopen`` and users can start scripts from ``noexec`` filesystem starting interpreter directly.
+Restrscript LSM submodule provides a way to restrict some programms to be executed directly, but allows to execute them as shebang handler.
+
+Sysctl parameters and defaults:
+
+* ``kernel.altha.rstrscript.enabled = 0``, set to 1 to enable
+* ``kernel.altha.rstrscript.interpreters =``, colon-separated list of restricted interpreters for example: ``/lib64/ld-linux-x86-64.so.2:/usr/bin/python:/usr/bin/python3:/usr/bin/perl:/usr/bin/tclsh``. Symlinks are supported in both ways: you can set symlink to interpreter as exception and interpreter and all symlinks on it will be restricted.
+
+Adding ld-linux into blocking list prevents running interpreters via ``ld-linux interpreter``.
+
+Note: in this configuration all scripts starting with ``#!/usr/bin/env python`` will be blocked.
+
+OLock
+============
+Unlink disabling for open files needed for Russian certification, but this is a nasty feature leading to DOS.
+
+Sysctl parameters and defaults:
+
+* ``kernel.altha.olock.enabled = 0``, set to 1 to enable
+* ``kernel.altha.olock.dirs =``, colon-separated list of dirs, for example: ``/var/lib/something:/tmp/something``.
+
+Kiosk
+===========
+Disable execution for everything and everyone (including system users
+and root, if required) except given whitelists.
+
+Kiosk interface uses generic netlink framework.
+Interface name: ``altha``
+
+Kiosk packet attributes::
+
+        static struct nla_policy kiosk_attrs_policy[KIOSK_MAX_ATTR] = {
+                [KIOSK_ACTION] = {
+                        .type = NLA_S16,
+                },
+                [KIOSK_DATA] = {
+                        .type = NLA_STRING,
+                        .maxlen = MAX_DATA /* 1024 */
+                },
+        };
+
+Possible kiosk modes::
+
+        enum kiosk_mode {
+                KIOSK_PERMISSIVE = 0, /* kiosk is disabled */
+                KIOSK_NONSYSTEM,      /* kiosk is enabled for users with uid >= 500 */
+                KIOSK_ALL,            /* kiosk is enabled for all users */
+        };
+
+In ``KIOSK_ALL`` mode root will be restricted if running from tty
+Otherwise application will be executed anyway,
+enabling the system to boot without garbage in whitelists.
+
+Possible kiosk actions::
+
+        enum altha_kiosk_action {
+                KIOSK_SET_MODE = 0, /* set or get mode, see below */
+                KIOSK_USERLIST_ADD, /* add app to user whitelist */
+                KIOSK_USERLIST_DEL, /* remove app from user whitelist */
+                KIOSK_SYSLIST_ADD,  /* add app to system whitelist */
+                KIOSK_SYSLIST_DEL,  /* remove app from system whitelist */
+                KIOSK_USER_LIST,    /* retrieve user whitelist, see below */
+                KIOSK_SYSTEM_LIST,  /* retrieve system whitelist */
+        };
+
+``KIOSK_ACTION`` attribute is used.
+
+``SET_MODE`` action will send current mode if ``KIOSK_DATA`` is empty.
+
+When ``KIOSK_USER_LIST`` or ``KIOSK_SYSTEM_LIST`` action is requested, kernel sends
+the first item from the list and waits for another request.
+When end of list is reached, it sends an empty string and it will be safe
+for client to request another list.
+
+``LD_*`` cheats will not be applied when kiosk is activated.
diff --git a/Documentation/admin-guide/LSM/index.rst b/Documentation/admin-guide/LSM/index.rst
index a6ba95fbaa9f..20b57e7adadd 100644
--- a/Documentation/admin-guide/LSM/index.rst
+++ b/Documentation/admin-guide/LSM/index.rst
@@ -47,3 +47,4 @@ subdirectories.
    tomoyo
    Yama
    SafeSetID
+   AltHa
diff --git a/security/Kconfig b/security/Kconfig
index 0ced7fd33e4d..42a72aa188dd 100644
--- a/security/Kconfig
+++ b/security/Kconfig
@@ -239,6 +239,8 @@ source "security/yama/Kconfig"
 source "security/safesetid/Kconfig"
 source "security/lockdown/Kconfig"
 source "security/landlock/Kconfig"
+source "security/altha/Kconfig"
+source "security/kiosk/Kconfig"
 
 source "security/integrity/Kconfig"
 
@@ -282,7 +284,7 @@ config LSM
 	default "landlock,lockdown,yama,loadpin,safesetid,integrity,apparmor,selinux,smack,tomoyo,bpf" if DEFAULT_SECURITY_APPARMOR
 	default "landlock,lockdown,yama,loadpin,safesetid,integrity,tomoyo,bpf" if DEFAULT_SECURITY_TOMOYO
 	default "landlock,lockdown,yama,loadpin,safesetid,integrity,bpf" if DEFAULT_SECURITY_DAC
-	default "landlock,lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf"
+	default "landlock,lockdown,yama,loadpin,safesetid,integrity,selinux,smack,tomoyo,apparmor,bpf,altha,kiosk"
 	help
 	  A comma-separated list of LSMs, in initialization order.
 	  Any LSMs left off this list will be ignored. This can be
diff --git a/security/Makefile b/security/Makefile
index 18121f8f85cd..4fb7ea65d2c9 100644
--- a/security/Makefile
+++ b/security/Makefile
@@ -24,6 +24,8 @@ obj-$(CONFIG_SECURITY_LOCKDOWN_LSM)	+= lockdown/
 obj-$(CONFIG_CGROUPS)			+= device_cgroup.o
 obj-$(CONFIG_BPF_LSM)			+= bpf/
 obj-$(CONFIG_SECURITY_LANDLOCK)		+= landlock/
+obj-$(CONFIG_SECURITY_ALTHA)		+= altha/
+obj-$(CONFIG_SECURITY_KIOSK)		+= kiosk/
 
 # Object integrity file lists
 obj-$(CONFIG_INTEGRITY)			+= integrity/
diff --git a/security/altha/Kconfig b/security/altha/Kconfig
new file mode 100644
index 000000000000..4bafdef4e58e
--- /dev/null
+++ b/security/altha/Kconfig
@@ -0,0 +1,11 @@
+config SECURITY_ALTHA
+	bool "AltHa security module"
+	depends on SECURITY
+	default n
+	help
+	  Some hardening options:
+	    * ignore SUID on binaries (with exceptions possible);
+	    * prevent running selected script interprers in interactive move;
+	    * WxorX for filesystems (with exceptions possible);
+
+	  If you are unsure how to answer this question, answer N.
diff --git a/security/altha/Makefile b/security/altha/Makefile
new file mode 100644
index 000000000000..56735b157567
--- /dev/null
+++ b/security/altha/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_SECURITY_ALTHA) := altha.o
+
+altha-y := altha_lsm.o
diff --git a/security/altha/altha_lsm.c b/security/altha/altha_lsm.c
new file mode 100644
index 000000000000..c670ad7ed458
--- /dev/null
+++ b/security/altha/altha_lsm.c
@@ -0,0 +1,351 @@
+/*
+ * AltHa Linux Security Module
+ *
+ * Author: Anton Boyarshinov <boyarsh@altlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/lsm_hooks.h>
+#include <linux/cred.h>
+#include <linux/sysctl.h>
+#include <linux/binfmts.h>
+#include <linux/file.h>
+#include <linux/ratelimit.h>
+#include <linux/moduleparam.h>
+#include <linux/list.h>
+#include <linux/namei.h>
+#include <linux/namei.h>
+#include <linux/printk.h>
+#include <linux/rwsem.h>
+#include <asm/uaccess.h>
+
+#define ALTHA_PARAMS_SIZE 4096
+char proc_nosuid_exceptions[ALTHA_PARAMS_SIZE];
+char proc_interpreters[ALTHA_PARAMS_SIZE];
+char proc_olock_dirs[ALTHA_PARAMS_SIZE];
+
+/* Boot time disable flag */
+static bool altha_enabled = 0;
+
+/* sysctl flags */
+static int nosuid_enabled;
+static int rstrscript_enabled;
+static int olock_enabled;
+
+/* Boot parameter handing */
+module_param_named(enabled, altha_enabled, bool, S_IRUGO);
+
+static int __init altha_enabled_setup(char *str)
+{
+	unsigned long enabled;
+	int error = kstrtoul(str, 0, &enabled);
+	if (!error)
+		altha_enabled = enabled ? 1 : 0;
+	return 1;
+}
+
+__setup("altha=", altha_enabled_setup);
+
+struct altha_list_struct {
+	struct path path;
+	char * spath;
+	char * spath_p;
+	struct list_head list;
+};
+
+/* Lists handling */
+DECLARE_RWSEM(nosuid_exceptions_sem);
+DECLARE_RWSEM(interpreters_sem);
+DECLARE_RWSEM(olock_dirs_sem);
+LIST_HEAD(nosuid_exceptions_list);
+LIST_HEAD(interpreters_list);
+LIST_HEAD(olock_dirs_list);
+
+static int altha_list_handler(struct ctl_table *table, int write,
+			      void __user * buffer, size_t * lenp,
+			      loff_t * ppos)
+{
+	struct altha_list_struct *item, *tmp;
+	struct list_head *list_struct;
+	char *p, *fluid;
+	char *copy_buffer;
+	struct rw_semaphore *sem = table->extra2;
+	unsigned long error = proc_dostring(table, write, buffer, lenp, ppos);
+	down_write(sem);
+	if (error)
+		goto out;
+
+	if (write && !error) {
+		copy_buffer = kmalloc(ALTHA_PARAMS_SIZE, GFP_KERNEL);
+		if (!copy_buffer) {
+			pr_err
+			    ("AltHa: can't get memory for copy_buffer processing sysctl\n");
+			error = -1;
+			goto out;
+		}
+
+		list_struct = (struct list_head *)(table->extra1);
+		/*empty list and that fill with new info */
+		list_for_each_entry_safe(item, tmp, list_struct, list) {
+			list_del(&item->list);
+			path_put(&item->path);
+			kfree(item->spath_p);
+			kfree(item);
+		}
+
+		strlcpy(copy_buffer, table->data, ALTHA_PARAMS_SIZE);
+
+		/* buffer can have a garbage after \n */
+		p = strchrnul(copy_buffer, '\n');
+		*p = 0;
+
+		/* for strsep usage */
+		fluid = copy_buffer;
+
+		while ((p = strsep(&fluid, ":\n")) != NULL) {
+			if (strlen(p)) {
+				item = kmalloc(sizeof(*item), GFP_KERNEL);
+				if (item)
+					item->spath_p = kmalloc(PATH_MAX, GFP_KERNEL);
+				if (!item || !item->spath_p) {
+					pr_err
+					    ("AltHa: can't get memory processing sysctl\n");
+					kfree(copy_buffer);
+					error = -1;
+					goto out;
+				}
+				if (kern_path(p, LOOKUP_FOLLOW, &item->path)) {
+					pr_info
+					    ("AltHa: error lookup '%s'\n", p);
+					kfree(item);
+				} else {
+					item->spath=d_path(&item->path,item->spath_p,PATH_MAX);
+					list_add_tail(&item->list, list_struct);
+				}
+			}
+		}
+		kfree(copy_buffer);
+	}
+out:
+	up_write(sem);
+	return error;
+}
+
+struct ctl_path nosuid_sysctl_path[] = {
+	{.procname = "kernel",},
+	{.procname = "altha",},
+	{.procname = "nosuid",},
+	{}
+};
+
+static struct ctl_table nosuid_sysctl_table[] = {
+	{
+	 .procname = "enabled",
+	 .data = &nosuid_enabled,
+	 .maxlen = sizeof(int),
+	 .mode = 0644,
+	 .proc_handler = proc_dointvec_minmax,
+	 },
+	{
+	 .procname = "exceptions",
+	 .data = proc_nosuid_exceptions,
+	 .maxlen = ALTHA_PARAMS_SIZE,
+	 .mode = 0644,
+	 .proc_handler = altha_list_handler,
+	 .extra1 = &nosuid_exceptions_list,
+	 .extra2 = &nosuid_exceptions_sem,
+	 },
+	{}
+};
+
+struct ctl_path rstrscript_sysctl_path[] = {
+	{.procname = "kernel",},
+	{.procname = "altha",},
+	{.procname = "rstrscript",},
+	{}
+};
+
+static struct ctl_table rstrscript_sysctl_table[] = {
+	{
+	 .procname = "enabled",
+	 .data = &rstrscript_enabled,
+	 .maxlen = sizeof(int),
+	 .mode = 0644,
+	 .proc_handler = &proc_dointvec_minmax,
+	 },
+	{
+	 .procname = "interpreters",
+	 .data = proc_interpreters,
+	 .maxlen = ALTHA_PARAMS_SIZE,
+	 .mode = 0644,
+	 .proc_handler = altha_list_handler,
+	 .extra1 = &interpreters_list,
+	 .extra2 = &interpreters_sem,
+	 },
+	{}
+};
+
+struct ctl_path olock_sysctl_path[] = {
+	{.procname = "kernel",},
+	{.procname = "altha",},
+	{.procname = "olock",},
+	{}
+};
+
+static struct ctl_table olock_sysctl_table[] = {
+	{
+	 .procname = "enabled",
+	 .data = &olock_enabled,
+	 .maxlen = sizeof(int),
+	 .mode = 0644,
+	 .proc_handler = &proc_dointvec_minmax,
+	 },
+	{
+	 .procname = "dirs",
+	 .data = proc_olock_dirs,
+	 .maxlen = ALTHA_PARAMS_SIZE,
+	 .mode = 0644,
+	 .proc_handler = altha_list_handler,
+	 .extra1 = &olock_dirs_list,
+	 .extra2 = &olock_dirs_sem,
+	 },
+	{}
+};
+
+struct altha_readdir_callback {
+	struct dir_context ctx;
+	u64 inode;
+	int found;
+};
+
+int is_olock_dir(struct inode *inode)
+{
+	struct altha_list_struct *node;
+	down_read(&olock_dirs_sem);
+	list_for_each_entry(node, &olock_dirs_list, list) {
+		struct inode *exc_inode = node->path.dentry->d_inode;
+		if (exc_inode == inode) {
+			up_read(&olock_dirs_sem);
+			return 1;
+		}
+	}
+	up_read(&olock_dirs_sem);
+	return 0;
+}
+
+/* Hooks */
+static int altha_bprm_creds_from_file(struct linux_binprm *bprm, struct file * fi)
+{
+	struct altha_list_struct *node;
+	/* when it's not a shebang issued script interpreter */
+	if (rstrscript_enabled && bprm->executable == bprm->interpreter) {
+		char *path_p;
+		char *path_buffer;
+
+		path_buffer = kmalloc(PATH_MAX, GFP_KERNEL);
+		if (!path_buffer)
+			return -ENOMEM;
+
+		path_p = d_path(&bprm->file->f_path,path_buffer,PATH_MAX);
+		down_read(&interpreters_sem);
+		list_for_each_entry(node, &interpreters_list, list) {
+			if (strcmp(path_p, node->spath) == 0) {
+				uid_t cur_uid = from_kuid(bprm->cred->user_ns,
+							  bprm->cred->uid);
+				pr_notice_ratelimited
+				    ("AltHa/RestrScript: %s is blocked to run directly by %d\n",
+				     bprm->filename, cur_uid);
+				up_read(&interpreters_sem);
+				kfree(path_buffer);
+				return -EPERM;
+			}
+		}
+		up_read(&interpreters_sem);
+		kfree(path_buffer);
+	}
+	if (unlikely(nosuid_enabled &&
+		     !uid_eq(bprm->cred->uid, bprm->cred->euid))) {
+		char *path_p;
+		char *path_buffer;
+		uid_t cur_uid;
+
+		path_buffer = kmalloc(PATH_MAX, GFP_KERNEL);
+		if (!path_buffer)
+			return -ENOMEM;
+
+		cur_uid = from_kuid(bprm->cred->user_ns, bprm->cred->uid);
+		path_p = d_path(&bprm->file->f_path,path_buffer,PATH_MAX);
+		down_read(&nosuid_exceptions_sem);
+		list_for_each_entry(node, &nosuid_exceptions_list, list) {
+			if (strcmp(path_p, node->spath) == 0) {
+				pr_notice_ratelimited
+				    ("AltHa/NoSUID: %s permitted to setuid from %d\n",
+				     bprm->filename, cur_uid);
+				up_read(&nosuid_exceptions_sem);
+				kfree(path_buffer);
+				return 0;
+			}
+		}
+		up_read(&nosuid_exceptions_sem);
+		pr_notice_ratelimited
+		    ("AltHa/NoSUID: %s prevented to setuid from %d\n",
+		     bprm->filename, cur_uid);
+		bprm->cred->euid = bprm->cred->uid;
+		kfree(path_buffer);
+	}
+	return 0;
+}
+
+/* For OLock */
+static int altha_inode_unlink(struct inode *inode, struct dentry *dentry)
+{
+	if (olock_enabled && (atomic_read(&dentry->d_inode->i_writecount)
+#ifdef CONFIG_IMA
+			|| atomic_read(&dentry->d_inode->i_readcount)
+#endif
+			)) {
+		if (is_olock_dir(inode))
+			return -EPERM;
+	}
+	return 0;
+}
+
+/* Initialization */
+
+static struct security_hook_list altha_hooks[] = {
+	LSM_HOOK_INIT(bprm_creds_from_file, altha_bprm_creds_from_file),
+	LSM_HOOK_INIT(inode_unlink, altha_inode_unlink),
+};
+
+static int __init altha_init(void)
+{
+	if (altha_enabled) {
+		pr_info("AltHa enabled.\n");
+		security_add_hooks(altha_hooks, ARRAY_SIZE(altha_hooks),"altha");
+
+		if (!register_sysctl_paths
+		    (nosuid_sysctl_path, nosuid_sysctl_table))
+			panic("AltHa: NoSUID sysctl registration failed.\n");
+
+		if (!register_sysctl_paths
+		    (rstrscript_sysctl_path, rstrscript_sysctl_table))
+			panic
+			    ("AltHa: RestrScript sysctl registration failed.\n");
+
+		if (!register_sysctl_paths
+		    (olock_sysctl_path, olock_sysctl_table))
+			panic("AltHa: OLock sysctl registration failed.\n");
+	} else
+		pr_info("AltHa disabled.\n");
+	return 0;
+}
+
+DEFINE_LSM(altha) = {
+       .name = "altha",
+       .init = altha_init,
+};
+
diff --git a/security/kiosk/Kconfig b/security/kiosk/Kconfig
new file mode 100644
index 000000000000..c92214abf62f
--- /dev/null
+++ b/security/kiosk/Kconfig
@@ -0,0 +1,9 @@
+config SECURITY_KIOSK
+	bool "kiosk module"
+	depends on SECURITY
+	default n
+	help
+	  Implements "Kiosk mode", in which user can be restricted to run anything
+	  not permitted by admin.
+
+	  If you are unsure how to answer this question, answer N.
diff --git a/security/kiosk/Makefile b/security/kiosk/Makefile
new file mode 100644
index 000000000000..d29aba92bb3e
--- /dev/null
+++ b/security/kiosk/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_SECURITY_KIOSK) := kiosk.o
+
+kiosk-y := kiosk_lsm.o
diff --git a/security/kiosk/kiosk-test.sh b/security/kiosk/kiosk-test.sh
new file mode 100755
index 000000000000..9ab8774183e6
--- /dev/null
+++ b/security/kiosk/kiosk-test.sh
@@ -0,0 +1,252 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Kiosk test suite: just run the script
+#
+
+try_run() {
+  echo "Test: $*" >&2
+  if "$@"; then
+     echo "Success." >&2
+  else
+     echo "Failed: exit code $?" >&2
+     exit 1
+  fi
+}
+
+# File appending and removing
+
+clean () {
+  kiosk -m "0"
+
+  for i in `kiosk --user-list`
+  do
+    kiosk -U "$i"
+  done
+}
+
+check_empty() {
+  TMPFILE=$(mktemp)
+  try_run kiosk --user-list > $TMPFILE
+
+  if [ -s "$TMPFILE" ]
+  then
+    echo "Failed: lists are not empty" >&2
+    rm -f $TMPFILE
+    exit 1
+  fi
+
+  rm -f $TMPFILE
+}
+
+kiosk_user_append() {
+  echo `readlink -f "$1"` >> $TMPFILE
+  try_run kiosk --user-list-append "$1"
+}
+
+kiosk_user_remove() {
+  try_run kiosk --user-list-remove "$1"
+}
+
+kiosk_user_list_check() {
+  TMPFILE=$1
+  LISTFILE=$(mktemp)
+  try_run kiosk --user-list > $LISTFILE
+
+  for i in `cat "$LISTFILE"`
+  do
+    try_run kiosk_user_remove "$i"
+  done
+
+  if cmp --quiet $TMPFILE $LISTFILE; then
+    echo "Success: user-list match" >&2
+  else
+    echo "Failed: user-list does not match" >&2
+    diff -u $TMPFILE $LISTFILE
+    exit 1
+  fi
+
+  rm -f $TMPFILE $LISTFILE
+}
+
+TMPFILE=$(mktemp)
+
+clean
+
+kiosk_user_append /bin/sh
+kiosk_user_append /bin/bash
+kiosk_user_append /bin/date
+kiosk_user_append /bin/ls
+kiosk_user_list_check "$TMPFILE"
+
+# Mode changing
+kiosk_set_mode() {
+  echo "$1" > $TMPFILE
+  try_run kiosk --set-mode "$1"
+}
+
+kiosk_check_mode() {
+  TMPFILE=$1
+  MODEFILE=$(mktemp)
+  try_run kiosk --get-mode > $MODEFILE
+
+  if cmp --quiet $TMPFILE $MODEFILE; then
+    echo "Success: mode match" >&2
+  else
+    echo "Failed: mode does not match" >&2
+    exit 1
+  fi
+
+  rm -rf $TMPFILE $MODEFILE
+}
+
+check_empty
+
+TMPFILE=$(mktemp)
+
+kiosk_set_mode "1"
+kiosk_check_mode "$TMPFILE"
+kiosk_set_mode "0"
+kiosk_check_mode "$TMPFILE"
+
+# Exec testing
+try_exec() {
+  REACT=$1
+  shift
+
+  echo "Executing $@" >&2
+  "$@" >/dev/null
+  if [ "x$REACT" = "xdeny" -a $? -ne 126 ]
+  then
+    echo "Error: application was executed while it should not be" >&2
+    echo "React is $REACT, error code is $?" >&2
+    exit 1
+  fi
+  if [ "x$REACT" = "xperm" -a $? -eq 126 ]
+  then
+    echo "Error: application was not executed while it should be" >&2
+    echo "React is $REACT, error code is $?" >&2
+    exit 1
+  fi
+}
+
+try_exec_user() {
+  REACT=$1
+  shift
+
+  try_exec $1 su - -c \"$@\" test
+}
+
+check_empty
+
+#necessary
+raise_guard() {
+  kiosk_user_append /bin/bash
+  kiosk_user_append /usr/bin/id
+  kiosk_user_append /bin/egrep
+  kiosk_user_append /bin/grep
+  kiosk_user_append /bin/hostname
+  kiosk_user_append /usr/bin/natspec
+  kiosk_user_append /usr/share/console-scripts/vt_activate_unicode
+  kiosk_user_append /usr/share/console-scripts/vt_activate_user_map
+  kiosk_user_append /sbin/consoletype
+}
+
+stop_guard() {
+  kiosk_user_remove /bin/bash
+  kiosk_user_remove /usr/bin/id
+  kiosk_user_remove /bin/egrep
+  kiosk_user_remove /bin/grep
+  kiosk_user_remove /bin/hostname
+  kiosk_user_remove /usr/bin/natspec
+  kiosk_user_remove /usr/share/console-scripts/vt_activate_unicode
+  kiosk_user_remove /usr/share/console-scripts/vt_activate_user_map
+  kiosk_user_remove /sbin/consoletype
+}
+
+raise_guard
+kiosk_user_append /bin/false
+
+kiosk_set_mode "0"
+try_exec_user perm /bin/false
+try_exec_user perm /bin/true
+kiosk_set_mode "1"
+try_exec_user perm /bin/false
+try_exec_user deny /bin/true
+kiosk_set_mode "0"
+
+kiosk_user_remove /bin/false
+stop_guard
+
+# TODO:
+# bogus append to list (non-exist file)
+check_empty
+kiosk_user_append /bin/tru
+
+# bogus append to list (no params)
+kiosk_user_append ""
+kiosk_user_append
+
+kiosk_user_append /bin/true
+kiosk_user_append /bin/false
+
+# bogus remove from list (non-exist file)
+kiosk_user_remove /bin/tru
+
+# bogus remove from list (file is not in list)
+kiosk_user_remove /bin/date
+kiosk_user_remove /bin/false
+kiosk_user_remove /bin/true
+
+# bogus remove from list (list is empty)
+check_empty
+kiosk_user_remove /bin/false
+
+# bogus remove from list (no params)
+kiosk_user_remove ""
+kiosk_user_remove
+
+# bogus mode (no params)
+kiosk_set_mode ""
+
+# bogus mode
+kiosk_set_mode "3"
+
+FILE="/home/test/test"
+cp /bin/true $FILE
+kiosk_user_append $FILE
+kiosk_set_mode "1"
+
+raise_guard
+
+# user executes his own script
+chmod 555 $FILE
+chown test $FILE
+chgrp test $FILE
+try_exec_user deny $FILE
+
+# user executes his script with his group
+chown root $FILE
+try_exec_user perm $FILE
+chmod g+w $FILE
+try_exec_user deny $FILE
+chmod g-w $FILE
+
+# user executes script without permissions
+chgrp root $FILE
+try_exec_user perm $FILE
+chmod o+w $FILE
+try_exec_user deny $FILE
+chmod o-w $FILE
+
+chmod 000 $FILE
+setfacl -m "u:test:rwx" $FILE
+try_exec_user deny $FILE
+setfacl -m "u:test:r-x" $FILE
+try_exec_user perm $FILE
+getfacl $FILE
+
+stop_guard
+
+kiosk_set_mode "0"
+kiosk_user_remove $FILE
+rm -fv $FILE
diff --git a/security/kiosk/kiosk_lsm.c b/security/kiosk/kiosk_lsm.c
new file mode 100644
index 000000000000..cf7a7df65995
--- /dev/null
+++ b/security/kiosk/kiosk_lsm.c
@@ -0,0 +1,337 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Kiosk Linux Security Module
+ *
+ * Author: Oleg Solovyov <mcpain@altlinux.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2, as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/lsm_hooks.h>
+#include <linux/cred.h>
+#include <linux/binfmts.h>
+#include <linux/list.h>
+#include <linux/namei.h>
+#include <linux/printk.h>
+#include <linux/rwsem.h>
+#include <linux/tty.h>
+
+#include <net/genetlink.h>
+
+#define MAX_PATH 1024
+
+struct kiosk_list_struct {
+	struct path path;
+	struct list_head list;
+};
+
+static struct kiosk_list_struct *list_iter;
+static struct genl_family genl_kiosk_family;
+static char pathbuf[MAX_PATH];
+
+/* Lists handling */
+static DECLARE_RWSEM(user_sem);
+static LIST_HEAD(user_list);
+
+enum kiosk_cmd {
+	KIOSK_UNSPEC = 0,
+	KIOSK_REQUEST,
+	KIOSK_REPLY,
+	KIOSK_CMD_LAST,
+};
+
+enum kiosk_mode {
+	KIOSK_PERMISSIVE = 0,
+	KIOSK_NONSYSTEM,
+	KIOSK_MODE_LAST,
+};
+
+static int kiosk_mode = KIOSK_PERMISSIVE;
+
+enum kiosk_action {
+	KIOSK_SET_MODE = 0,
+	KIOSK_USERLIST_ADD,
+	KIOSK_USERLIST_DEL,
+	KIOSK_USER_LIST,
+};
+
+enum kiosk_attrs {
+	KIOSK_NOATTR = 0,
+	KIOSK_ACTION,
+	KIOSK_DATA,
+	KIOSK_MAX_ATTR,
+};
+
+static struct nla_policy kiosk_policy[KIOSK_MAX_ATTR] = {
+	[KIOSK_ACTION] = {
+		.type = NLA_S16,
+	},
+	[KIOSK_DATA] = {
+		.type = NLA_STRING,
+		.len = sizeof(pathbuf) - 1
+	},
+};
+
+static int kiosk_add_item(struct list_head *list, char *filename,
+		struct rw_semaphore *sem)
+{
+	struct kiosk_list_struct *item, *tmp;
+	int mode;
+	int rc;
+
+	item = kmalloc(sizeof(*item), GFP_KERNEL);
+	if (!item)
+		return -ENOMEM;
+
+	rc = kern_path(filename, LOOKUP_FOLLOW, &item->path);
+	if (rc) {
+		pr_err("Kiosk: error lookup '%s'\n", filename);
+		kfree(item);
+		return rc;
+	}
+
+	mode = d_inode(item->path.dentry)->i_mode;
+	if (!S_ISREG(mode)) {
+		pr_err("Kiosk: given file is not a regular file, mode: %d\n",
+		       mode);
+		path_put(&item->path);
+		kfree(item);
+		return -EINVAL;
+	}
+
+	down_write(sem);
+	list_for_each_entry(tmp, list, list) {
+		if (item->path.dentry == tmp->path.dentry) {
+			up_write(sem);
+			path_put(&item->path);
+			kfree(item);
+			return 0;
+		}
+	}
+	list_add_tail(&item->list, list);
+	up_write(sem);
+
+	return 0;
+}
+
+static int kiosk_remove_item(struct list_head *list, char *filename,
+			     struct rw_semaphore *sem)
+{
+	struct kiosk_list_struct *item, *tmp;
+	struct path user_path;
+	int rc;
+
+	rc = kern_path(filename, LOOKUP_FOLLOW, &user_path);
+	if (rc)
+		return rc;
+
+	down_write(sem);
+	list_for_each_entry_safe(item, tmp, list, list) {
+		if (item->path.dentry == user_path.dentry) {
+			if (item == list_iter) {
+				pr_err("Kiosk: list is being iterated, item removing is unsafe\n");
+				up_write(sem);
+				path_put(&user_path);
+				return -EAGAIN;
+			}
+			list_del(&item->list);
+			path_put(&item->path);
+			kfree(item);
+		}
+	}
+	up_write(sem);
+	path_put(&user_path);
+	return 0;
+}
+
+static int kiosk_nl_send_msg(struct sk_buff *skb, struct genl_info *info,
+			     char *msg)
+{
+	int msg_size;
+	int res;
+	struct nlmsghdr *nlh;
+	struct sk_buff *skb_out;
+
+	msg_size = strlen(msg) + 1;
+	/* we put string so add space for NUL-terminator */
+
+	skb_out = genlmsg_new(msg_size, GFP_KERNEL);
+	if (!skb_out)
+		return -ENOMEM;
+
+	nlh = genlmsg_put_reply(skb_out, info, &genl_kiosk_family, 0,
+				KIOSK_REPLY);
+	if (!nlh) {
+		nlmsg_free(skb_out);
+		return -ENOMEM;
+	}
+
+	res = nla_put_string(skb_out, KIOSK_DATA, msg);
+	if (res) {
+		nlmsg_free(skb_out);
+		return res;
+	}
+
+	genlmsg_end(skb_out, nlh);
+	return genlmsg_reply(skb_out, info);
+}
+
+static int kiosk_list_items(struct list_head *list, struct rw_semaphore *sem,
+			    struct sk_buff *skb, struct genl_info *info)
+{
+	char *path;
+
+	down_read(sem);
+
+	if (!list_iter) { /* list iterating started */
+		list_iter = list_first_entry_or_null(list,
+						     struct kiosk_list_struct,
+						     list);
+	} else if (list_iter == list_last_entry(list,
+						struct kiosk_list_struct,
+						list)) {
+		/* hit list end, cleaning temp variable */
+		list_iter = NULL;
+	} else { /* iterating list */
+		list_iter = list_next_entry(list_iter, list);
+	}
+
+	if (list_iter)
+		path = d_path(&list_iter->path, pathbuf, sizeof(pathbuf));
+	else
+		path = "";
+
+	up_read(sem);
+	return kiosk_nl_send_msg(skb, info, path);
+}
+
+static int kiosk_genl_doit(struct sk_buff *skb, struct genl_info *info)
+{
+	int action;
+
+	if (info->attrs[KIOSK_DATA])
+		strlcpy(pathbuf, nla_data(info->attrs[KIOSK_DATA]), sizeof(pathbuf));
+	else
+		pathbuf[0] = '\0';
+
+	action = info->attrs[KIOSK_ACTION] ?
+		nla_get_s16(info->attrs[KIOSK_ACTION]) : -1;
+
+	switch (action) {
+	case KIOSK_SET_MODE: {
+		int new_mode;
+		int error;
+		char buf[4];
+
+		if (!strlen(pathbuf)) {
+			/* we want to retrieve current mode */
+			snprintf(buf, sizeof(buf), "%d", kiosk_mode);
+			return kiosk_nl_send_msg(skb, info, buf);
+		}
+
+		error = kstrtouint(pathbuf, 0, &new_mode);
+
+		if (error || new_mode < 0
+			  || new_mode >= KIOSK_MODE_LAST) {
+			return -EINVAL;
+		}
+		kiosk_mode = new_mode;
+		return 0;
+	}
+	case KIOSK_USERLIST_ADD:
+		return kiosk_add_item(&user_list, pathbuf, &user_sem);
+	case KIOSK_USERLIST_DEL:
+		return kiosk_remove_item(&user_list, pathbuf,
+					 &user_sem);
+	case KIOSK_USER_LIST:
+		return kiosk_list_items(&user_list, &user_sem, skb,
+					info);
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct genl_ops genl_kiosk_ops[] = {
+	{
+		.doit = kiosk_genl_doit,
+		.flags = GENL_ADMIN_PERM,
+	},
+};
+
+static struct genl_family genl_kiosk_family = {
+	.name = "kiosk",
+	.version = 1,
+	.netnsok = false,
+	.module = THIS_MODULE,
+	.ops = genl_kiosk_ops,
+	.n_ops = ARRAY_SIZE(genl_kiosk_ops),
+	.maxattr = KIOSK_MAX_ATTR,
+	.policy = kiosk_policy,
+};
+
+/* Hooks */
+static int kiosk_bprm_check_security(struct linux_binprm *bprm)
+{
+	uid_t cur_uid = __kuid_val(bprm->cred->uid);
+	struct kiosk_list_struct *node;
+
+	if (kiosk_mode == KIOSK_PERMISSIVE)
+		return 0;
+
+	if (cur_uid >= 500) {
+		bprm->secureexec = 1;
+		if (bprm->executable != bprm->interpreter)
+			return 0;
+
+		if (cur_uid == __kuid_val(bprm->file->f_inode->i_uid) ||
+		    (bprm->file->f_inode->i_mode & 0022)) {
+			pr_notice_ratelimited("Kiosk: %s is writable for %d\n",
+					      bprm->filename, cur_uid);
+			return -EPERM;
+		}
+
+		down_read(&user_sem);
+		list_for_each_entry(node, &user_list, list) {
+			if (bprm->file->f_path.dentry == node->path.dentry) {
+				up_read(&user_sem);
+				return 0;
+			}
+		}
+		up_read(&user_sem);
+	} else {
+		return 0;
+	}
+
+	pr_notice_ratelimited("Kiosk: %s prevented to exec from %d\n",
+			      bprm->filename, cur_uid);
+	return -EPERM;
+}
+
+static struct security_hook_list kiosk_hooks[] = {
+	LSM_HOOK_INIT(bprm_check_security, kiosk_bprm_check_security),
+};
+
+static int __init kiosk_init(void)
+{
+	int rc;
+
+	rc = genl_register_family(&genl_kiosk_family);
+
+	if (rc) {
+		pr_alert("Kiosk: Error registering family.\n");
+		return rc;
+	}
+
+	pr_info("Kiosk: Netlink family registered.\n");
+	security_add_hooks(kiosk_hooks, ARRAY_SIZE(kiosk_hooks), "kiosk");
+
+	return 0;
+}
+
+DEFINE_LSM(kiosk) = {
+	.name = "kiosk",
+	.init = kiosk_init,
+};
 
дизайн и разработка: Vladimir Lettiev aka crux © 2004-2005, Andrew Avramenko aka liks © 2007-2008
текущий майнтейнер: Michael Shigorin