From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756513AbcKWJkj (ORCPT ); Wed, 23 Nov 2016 04:40:39 -0500 Received: from erza.lautre.net ([80.67.160.89]:38596 "EHLO erza.lautre.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933789AbcKWJkT (ORCPT ); Wed, 23 Nov 2016 04:40:19 -0500 X-Greylist: delayed 351 seconds by postgrey-1.27 at vger.kernel.org; Wed, 23 Nov 2016 04:40:18 EST X-Spam-Flag: NO X-Spam-Score: -3 From: jobol@nonadev.net To: linux-security-module@vger.kernel.org, linux-kernel@vger.kernel.org Cc: =?UTF-8?q?Jos=C3=A9=20Bollo?= Subject: [RFC 1/1] LSM ptags: Add tagging of processes Date: Wed, 23 Nov 2016 10:33:17 +0100 Message-Id: <1479893597-5022-2-git-send-email-jobol@nonadev.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1479893597-5022-1-git-send-email-jobol@nonadev.net> References: <1479893597-5022-1-git-send-email-jobol@nonadev.net> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: José Bollo The ptags module allows to attach tags to processes. Tags are accessed through the new files /proc/PID/attr/ptags and /proc/PID/tasks/TID/attr/ptags (named below "ptag file"). See Documentation/security/ptags.txt for details Signed-off-by: José Bollo --- Documentation/security/ptags.txt | 428 +++++++ fs/proc/base.c | 3 + include/linux/cred.h | 3 + include/linux/user_namespace.h | 23 + kernel/cred.c | 6 + kernel/user_namespace.c | 3 + security/Kconfig | 1 + security/Makefile | 2 + security/apparmor/lsm.c | 14 +- security/ptags/Kconfig | 16 + security/ptags/Makefile | 6 + security/ptags/ptags-lsm.c | 213 ++++ security/ptags/ptags.c | 2377 ++++++++++++++++++++++++++++++++++++++ security/selinux/hooks.c | 8 + security/smack/smack_lsm.c | 14 +- 15 files changed, 3113 insertions(+), 4 deletions(-) create mode 100644 Documentation/security/ptags.txt create mode 100644 security/ptags/Kconfig create mode 100644 security/ptags/Makefile create mode 100644 security/ptags/ptags-lsm.c create mode 100644 security/ptags/ptags.c diff --git a/Documentation/security/ptags.txt b/Documentation/security/ptags.txt new file mode 100644 index 0000000..ef3c131 --- /dev/null +++ b/Documentation/security/ptags.txt @@ -0,0 +1,428 @@ +The PTAGS security module +========================= + + +Overview +-------- + +The PTAGS module allows to attach tags to processes. Tags are accessed +through the files /proc//attr/ptags and /proc//tasks//attr/ptags +(named below "ptags file"). + +The module PTAGS is part of the security of linux for 2 reasons: + + 1. it is built on top of the Linux Security Module (LSM) infrastructure + as it exists since v4.1 + + 2. it is a foundation for building permissions, privileges and cookies based + security in user land. + +The tags are public by nature. Accesses to the ptags file are not restricted +except by standards DAC and MAC for files: any process having read access to +/proc//attr/ptags or to /proc//tasks//attr/ptags can read the +tags of the task (and their value if any). + +Writing on tags files is also possible and subject to DAC and MAC rules. + +Writing a ptags file allows (under conditions) to change the tags of a +process. When writting the file, it acts like a protocol accepting +one or more lines. This protocol allows to add new tags, to remove existing +tags, to attach values to tags, to tell wether the tag is kept or not on +execution of the system call "execve". + +The accepted lines are: + + - empty line: "\n" + + Only a new-line + + - comment: "#...anything...\n" + + A sharp followed by anything and a new-line + + - action: + + Action lines begins with one of the following characters: + + o + (plus) adds a tag and/or set keep flags + o - (minus) removes tags and/or keep flags + o ! (bang) assign a value to a tag + o ? (question mark) query existence of tags and/or keep flags + + +Example of using ptags +---------------------- + +Usages of ptags are multiple. One of the main use is setting privileges, +permissions or cookies to processes in the user land. + +For this example, a server S will be queried to give permissions to a process. +The permissions given are handled by a simple tag: one permission, one tag. +Because the server S doesn't want to interfere with other permission managers, +it prefixes its permissions with the prefix "S:". + +To be able to add and remove permissions aof prefix "S:" to other processes, +the server process S must have the 3 special tags below: + + - ptags:S:add + - ptags:S:others + - ptags:S:sub + +This means that the process can add and sub tags prefixed by S: for itself and +for other processes. + +Having this tags, the server can now add tags prefixed by S: to other +processes by writing the ptags file /proc//attr/ptags as if the following +command was issued: + + $ echo "+S:PERMISSION-NAME" > /proc//attr/ptags + +This will add the tags S:PERMISSION-NAME to the process of . +In that case, the "keep flag" is not set. + +The fact that this means that the permission S:PERMISSION-NAME is given +to the process is only a convention between processes of the user land. +In the user land, checking if a process has or not the tag S:PERMISSION-NAME +is done by reading the ptags file /proc//attr/ptags as if the following +command was issued: + + $ grep -q '^S:PERMISSION-NAME$' /proc//attr/ptags + +The following command use ptags protocol and has the same behaviour: + + $ echo "?S:PERMISSION-NAME" > /proc//attr/ptags + +The role of the ptags module is to ensure that the given tags are attached +to the process and will die with it. + +The module ptags also allows the tags to be automatically copied to cloned +processes (forks and threads) and manages how tags are kept or not when +the system call "execve" is invoked. + +In the above example, the tag S:PERMISSION-NAME is removed when execve is +called because the keep flag is not set. + +To set the keep flag, an @ (at sign) must prefix the tag name in the + (add) +command, as if the following command was issued: + + $ echo "+@S:PERMISSION-NAME" > /proc//attr/ptags + +It is possible to attach a value to a tag. Thos achieve it, the process must +have the special tag "ptags:S:set". This is done by writing the ptags file +as with the command: + + $ echo "!S:PERMISSION-NAME=VALUE" > /proc//attr/ptags + + +Structure of tags +----------------- + +Tags are structured in fields separated by : (colon). For example, the tag +"scope:subscope:item" is made of 3 fields: "scope", "subscope" and "item". + +Tags whose first field is "ptags" are specials: they are used to give +permissions to modify tags. + +Note that fields can be empty: the tag ":x::a" is a valid tag. + +The structure of the tags allows to define prefix and pattern, the pattern +"scope:subscope:*" matches any tag that has the prefix "scope:subscope:". +Matching ":*" is allowed and matches any tag beginning with :. + + +Tag Validity (TV) Rules +----------------------- + +The following rules detail what are valid tags: + +(TV.1) + + Tags are valid utf-8 strings with a maximum length of 4000 bytes (the count + of char is lower or equal to 4000 because utf8 encoding). + +(TV.2) + + Tags must contain neither control characters (code lower than 32), + nor '=' (equal), nor '*' (star), nor DEL (ASCII code 127). (note that tags + can contain spaces but can't contain tab) + +(TV.3) + + Tags must neither begin with @ (at sign) nor end with : (colon). + +(TV.4) + + Tags starting with the string "ptags:" (6 characters) must end with + one of the following strings: ":add", ":sub", ":set", ":others". For + example, "ptags:add" (9 characters) and "ptags:myself:sub" (16 characters) + are valid tags but "ptags:myadd" (11 characters) is not valid. + + +Value Validity (VV) rules +------------------------- + +(VV.1) + + Values are valid utf-8 strings with a maximum length of 32700 bytes (the + count of char is lower or equal to 32700 because utf8 encoding). + +(VV.2) + + Values must contain neither control characters (code lower than 32), + nor DEL (ASCII code 127). (note that values can contain spaces but can't + contain tab) + + +Pattern Validity (PV) rules +--------------------------- + +(PV.1) + + Any valid tag (see TV) is a valid pattern matching only that tag. + This is an exact pattern. + +(PV.2) + + Any valid tag (see TV) followed by :* (the two characters colon then star) + is a valid pattern matching any tag prefixed by the string before * (star). + This is a global pattern. + +Example: the pattern "S:*" will match "S:A:B.c" and "S:X" but will not match +neither "S" nor "X". + +For actions matching global patterns, the action is performed where allowed and +silentely not performed where not allowed. + +Action of reading the ptags file +-------------------------------- + +Any process allowed to read the ptags file of an other process (depending on +DAC and MAC) has access to the tag set. + +Reading the ptags file returns valid utf8 lines, one tag per line. Each LINE +has the following format: + + LINE = KEEP-FLAG? TAG-NAME VALUE? LF + KEEP-FLAG = '@' + TAG-NAME = see TV rules + VALUE = '=' see VV rules + +The tags are listed in lexicographic order as below: + +a-tag +@b-tag +c-tag=value-of-c-tag + +The keep flag (@ at sign at start of the line) is ignored when sorting tags. +On the above example, the b-tag is kept across "execve" while other tags are +dropped. The value of c-tag is available for any reader after the equal sign. + + +Action of adding for one tag +---------------------------- + +The action of adding one tag of name TAGNAME is done by writing either +"+TAGNAME\n" or "+@TAGNAME\n" to the ptags file. + +The table below shows the effect of writing either +TAGNAME or +@TAGNAME +when adding is allowed. + + BEFORE : +TAGNAME : +@TAGNAME + ----------+-----------+--------------- + : TAGNAME : @TAGNAME + TAGNAME : TAGNAME : @TAGNAME + @TAGNAME : @TAGNAME : @TAGNAME + +The table below shows the effect of writing either +TAGNAME or +@TAGNAME +when adding is NOT allowed. + + BEFORE : +TAGNAME : +@TAGNAME + ----------+-----------+--------------- + : : + TAGNAME : TAGNAME : + @TAGNAME : @TAGNAME : @TAGNAME + + +Action of adding the keep flag for global patterns +-------------------------------------------------- + +The action of adding the keep flag to tags matching the global pattern GLOB:* +is done by writing "+@GLOB:*\n" to the ptags file. + +See the third columns of the tables above for effect of the command on tags +matching the pattern. + + +Action of removing for one tag +------------------------------ + +The action of removing the tag of name TAGNAME is done by writing +"-TAGNAME\n" to the ptags file. + +The action of removing the keep flag tag of name TAGNAME is done by writing +"-@TAGNAME\n" to the ptags file. + +The table below shows the effect of writing either -TAGNAME or -@TAGNAME +when removing is allowed. + + BEFORE : -TAGNAME : -@TAGNAME + ----------+-----------+--------------- + : : + TAGNAME : : TAGNAME + @TAGNAME : : TAGNAME + +The table below shows the effect of writing either -TAGNAME or -@TAGNAME +when removing is NOT allowed. + + BEFORE : -TAGNAME : -@TAGNAME + ----------+-----------+--------------- + : : + TAGNAME : TAGNAME : TAGNAME + @TAGNAME : @TAGNAME : @TAGNAME + + +Action of removing for global patterns +-------------------------------------- + +The action of removing tags matching the global pattern GLOB:* is done by +writing "-GLOB:*\n" to the ptags file. + +The action of removing the keep flag of tags matching the global pattern +GLOB:* is done by writing "-@GLOB:*\n" to the ptags file. + +See the tables above for effect of the command on tags matching the pattern. + +Action of removing all +---------------------- + +The action of removing all tags is done by writing "-\n" to the ptags file. + +The action of removing the keep flag of all tags is done by writing "-@\n" to +the ptags file. + +See the tables above for effect of the command on tags matching the pattern. + + +Action of setting the value of one tag +-------------------------------------- + +The action of attaching VALUE to the tag of name TAGNAME is done by writing +"!TAGNAME=VALUE\n" to the ptags file. + +The value can be removed (or set to nothing) by writing "!TAGNAME=\n" or +"!TAGNAMEn" to the ptags file. + + +Querying existence of tags or keep flags of tags +------------------------------------------------ + +Writing "?PATTERN\n" returns an error if no tags matching pattern exist. + +Writing "?@PATTERN\n" returns an error if no tags matching pattern exist or if +no tags matching pattern has the keep flag. + + +Allowed actions +--------------- + +Any process can query any other processes (? command). + +Normally a process can not remove tags or keep flags from itself. A process can +remove a tag or a keep flag from itself only if one or more of the following +conditions is true: + + - the process has not the tag or the keep flag (it is not an error) + + - the process has the tag "ptags:sub" and the tag to remove + or whose keep flag is to remove is not a special tag + + - the process has a tag "ptags:PREFIX:sub" where PREFIX is any valid prefix + and the tag to remove or whose keep flag is to remove has the prefix + "PREFIX:" (in particular, the tag "ptags:ptags:sub" allows to remove any + special tag) + + - the process is a kernel's thread + + - the process has the capability CAP_MAC_ADMIN according to user namespaces + +Normally a process can not add tags or keep flags to itself. A process can add +a tag or keep flags to itself only if one or more of the following conditions +is true: + + - the process already has the tag or the keep flag (it is not an error) + + - the process has the tag "ptags:add" and the tag to add or whose keep flags + is to set is not a special tag + + - the process has a tag "ptags:PREFIX:add" where PREFIX is any valid prefix + and the tag to add or whose keep flag is to set has the prefix "PREFIX:" + (in particular, the tag "ptags:ptags:add" allows to add any special tag) + + - the process is a kernel's thread + + - the process has the capability CAP_MAC_ADMIN according to user namespaces + +Normally a process can not set values to its tags. A process can set values +to its tags only if one or more of the following conditions is true: + + - the process has the tag "ptags:set" and the tag to set is not a special tag + + - the process has a tag "ptags:PREFIX:set" where PREFIX is any valid prefix + and the tag to set to set has the prefix "PREFIX:" + + - the process is a kernel's thread + + - the process has the capability CAP_MAC_ADMIN according to user namespaces + +Normally a process can not modify tags of other processes. A process can modify +the tag set of an other process only if one or more of the following conditions +is true: + + - the process is a kernel's thread + + - the process has the capability CAP_MAC_ADMIN according to user namespaces + + - the following two conditions are both true: + + o the process could modify the tags if it was for itself + + o one of the following condition is true: + + # the process has the tag "ptags:others" and tags to modify are not + special tags + + # the process has the tag "ptags:PREFIX:others" where PREFIX is any valid + prefix and tags to modify have the prefix "PREFIX:" (in particular, the + tag "ptags:ptags:others" allows to modify special tags of other + processes) + + +Writing to ptags files +---------------------- + +When writing lines to ptags file, using "write" call, the result is the count +of bytes written without error. If the first line raise an error, an error is +returned through errno. The returned errors are: + + error action reason + -------------------------------------------------------------- + EINVAL +-!? invalid value + EPERM +-! not allowed + ENOMEM +! out of memory + ENOENT -? no tag found + ECANCELED + maximum count of tags reached + + +Watching ptags files +-------------------- + +Because tags are modified only through accesses to the ptags file, it is +possible to monitor the inotify(7) interface to track possible changes. + + +User land library +----------------- + +A library for accessing and managing process tags is available for +download at https://gitlab.com/jobol/ptags + diff --git a/fs/proc/base.c b/fs/proc/base.c index ca651ac..94eb88e 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -2532,6 +2532,9 @@ static const struct pid_entry attr_dir_stuff[] = { REG("fscreate", S_IRUGO|S_IWUGO, proc_pid_attr_operations), REG("keycreate", S_IRUGO|S_IWUGO, proc_pid_attr_operations), REG("sockcreate", S_IRUGO|S_IWUGO, proc_pid_attr_operations), +#ifdef CONFIG_SECURITY_PTAGS + REG("ptags", S_IRUGO|S_IWUGO, proc_pid_attr_operations), +#endif }; static int proc_attr_dir_readdir(struct file *file, struct dir_context *ctx) diff --git a/include/linux/cred.h b/include/linux/cred.h index f0e70a1..34777e4 100644 --- a/include/linux/cred.h +++ b/include/linux/cred.h @@ -139,6 +139,9 @@ struct cred { #endif #ifdef CONFIG_SECURITY void *security; /* subjective LSM security */ +#ifdef CONFIG_SECURITY_PTAGS + void *ptags; /* secured process tagging */ +#endif #endif struct user_struct *user; /* real user ID subscription */ struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */ diff --git a/include/linux/user_namespace.h b/include/linux/user_namespace.h index eb209d4..8ae8c60 100644 --- a/include/linux/user_namespace.h +++ b/include/linux/user_namespace.h @@ -40,6 +40,9 @@ struct user_namespace { struct uid_gid_map gid_map; struct uid_gid_map projid_map; atomic_t count; +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + atomic_t weak_count; +#endif struct user_namespace *parent; int level; kuid_t owner; @@ -95,6 +98,26 @@ static inline void put_user_ns(struct user_namespace *ns) __put_user_ns(ns); } +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS +static inline struct user_namespace *get_weak_user_ns(struct user_namespace *ns) +{ + if (ns && atomic_inc_return(&ns->weak_count) == 1) + get_user_ns(ns); + return ns; +} + +static inline void put_weak_user_ns(struct user_namespace *ns) +{ + if (ns && atomic_dec_and_test(&ns->weak_count)) + put_user_ns(ns); +} + +static inline int is_weak_user_ns_still_alive(struct user_namespace *ns) +{ + return ns && atomic_read(&ns->count) > 1; +} +#endif + struct seq_operations; extern const struct seq_operations proc_uid_seq_operations; extern const struct seq_operations proc_gid_seq_operations; diff --git a/kernel/cred.c b/kernel/cred.c index 5f264fb..303f766 100644 --- a/kernel/cred.c +++ b/kernel/cred.c @@ -273,6 +273,9 @@ struct cred *prepare_creds(void) #ifdef CONFIG_SECURITY new->security = NULL; #endif +#ifdef CONFIG_SECURITY_PTAGS + new->ptags = NULL; +#endif if (security_prepare_creds(new, old, GFP_KERNEL) < 0) goto error; @@ -627,6 +630,9 @@ struct cred *prepare_kernel_cred(struct task_struct *daemon) #ifdef CONFIG_SECURITY new->security = NULL; #endif +#ifdef CONFIG_SECURITY_PTAGS + new->ptags = NULL; +#endif if (security_prepare_creds(new, old, GFP_KERNEL) < 0) goto error; diff --git a/kernel/user_namespace.c b/kernel/user_namespace.c index 86b7854..66a14b6 100644 --- a/kernel/user_namespace.c +++ b/kernel/user_namespace.c @@ -114,6 +114,9 @@ int create_user_ns(struct cred *new) ns->ns.ops = &userns_operations; atomic_set(&ns->count, 1); +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + atomic_set(&ns->weak_count, 0); +#endif /* Leave the new->user_ns reference with the new user namespace. */ ns->parent = parent_ns; ns->level = parent_ns->level + 1; diff --git a/security/Kconfig b/security/Kconfig index 118f454..68c1e10 100644 --- a/security/Kconfig +++ b/security/Kconfig @@ -164,6 +164,7 @@ source security/tomoyo/Kconfig source security/apparmor/Kconfig source security/loadpin/Kconfig source security/yama/Kconfig +source security/ptags/Kconfig source security/integrity/Kconfig diff --git a/security/Makefile b/security/Makefile index f2d71cd..85e7cd4 100644 --- a/security/Makefile +++ b/security/Makefile @@ -9,6 +9,7 @@ subdir-$(CONFIG_SECURITY_TOMOYO) += tomoyo subdir-$(CONFIG_SECURITY_APPARMOR) += apparmor subdir-$(CONFIG_SECURITY_YAMA) += yama subdir-$(CONFIG_SECURITY_LOADPIN) += loadpin +subdir-$(CONFIG_SECURITY_PTAGS) += ptags # always enable default capabilities obj-y += commoncap.o @@ -25,6 +26,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/ obj-$(CONFIG_SECURITY_YAMA) += yama/ obj-$(CONFIG_SECURITY_LOADPIN) += loadpin/ obj-$(CONFIG_CGROUP_DEVICE) += device_cgroup.o +obj-$(CONFIG_SECURITY_PTAGS) += ptags/ # Object integrity file lists subdir-$(CONFIG_INTEGRITY) += integrity diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 41b8cb1..8478b25 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -473,10 +473,16 @@ static int apparmor_getprocattr(struct task_struct *task, char *name, { int error = -ENOENT; /* released below */ - const struct cred *cred = get_task_cred(task); - struct aa_task_cxt *cxt = cred_cxt(cred); + const struct cred *cred; + struct aa_task_cxt *cxt; struct aa_profile *profile = NULL; +#ifdef CONFIG_SECURITY_PTAGS + if (strcmp(name, "ptags") == 0) + return 0; +#endif + cred = get_task_cred(task); + cxt = cred_cxt(cred); if (strcmp(name, "current") == 0) profile = aa_get_newest_profile(cxt->profile); else if (strcmp(name, "prev") == 0 && cxt->previous) @@ -504,6 +510,10 @@ static int apparmor_setprocattr(struct task_struct *task, char *name, size_t arg_size; int error; +#ifdef CONFIG_SECURITY_PTAGS + if (strcmp(name, "ptags") == 0) + return 0; +#endif if (size == 0) return -EINVAL; /* task can only write its own attributes */ diff --git a/security/ptags/Kconfig b/security/ptags/Kconfig new file mode 100644 index 0000000..ab383d11 --- /dev/null +++ b/security/ptags/Kconfig @@ -0,0 +1,16 @@ +config SECURITY_PTAGS + bool "Secure process tagging support (PTAGS)" + depends on SECURITY + default n + help + PTags module allows to tag the processes through + the special files /proc//attr/ptags. + +config SECURITY_PTAGS_WITH_USER_NS + bool "PTAGS with user namespace support (EXPERIMENTAL)" + depends on SECURITY_PTAGS && USER_NS + default n + help + Activating this option allows PTAGS module to + manage tags by user namespaces. + diff --git a/security/ptags/Makefile b/security/ptags/Makefile new file mode 100644 index 0000000..f15098f --- /dev/null +++ b/security/ptags/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the Tag module +# + +obj-$(CONFIG_SECURITY_PTAGS) := ptags-lsm.o + diff --git a/security/ptags/ptags-lsm.c b/security/ptags/ptags-lsm.c new file mode 100644 index 0000000..77bd19e --- /dev/null +++ b/security/ptags/ptags-lsm.c @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2016 José Bollo + * + * 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. + * + * Author: + * José Bollo + */ + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS +#include +#endif + +#include "ptags.c" + +#define set_ptags_of_cred(cred,value) ((cred)->ptags = value) +#define ptags_of_cred(cred) ((struct ptags*)((cred)->ptags)) +#define ptags_of_bprm(bprm) ptags_of_cred((bprm)->cred) +#define ptags_of_task(task) ((struct ptags*) \ + task_cred_xxx(task, ptags)) +#define ptags_of_current() ((struct ptags*) \ + current_cred_xxx(ptags)) + +/** + * ptags_is_ptags_file - Is 'name' of ptags entry? + * + * @name: the name to test + * + * Returns 1 when 'name' is the ptags entry name + * or, otherwise, returns 0. + */ +static int inline ptags_is_ptags_file(const char *name) +{ + return !strcmp(name, "ptags"); +} + +/** + * ptags_bprm_committing_creds - Prepare to install the new credentials + * from bprm. + * + * @bprm: binprm for exec + */ +static void ptags_bprm_committing_creds(struct linux_binprm *bprm) +{ + ptags_prune(ptags_of_bprm(bprm)); +} + +/** + * ptags_cred_alloc_blank - "allocate" blank task-level security credentials + * @new: the new credentials + * @gfp: the atomicity of any memory allocations + * + * Prepare a blank set of credentials for modification. This must allocate all + * the memory the LSM module might require such that cred_transfer() can + * complete without error. + */ +static int ptags_cred_alloc_blank(struct cred *cred, gfp_t gfp) +{ + struct ptags *root; + + root = ptags_create(); + set_ptags_of_cred(cred, root); + return root ? 0 : -ENOMEM; +} + +/** + * ptags_cred_free - "free" task-level security credentials + * @cred: the credentials in question + * + */ +static void ptags_cred_free(struct cred *cred) +{ + struct ptags *root; + + root = ptags_of_cred(cred); + set_ptags_of_cred(cred, NULL); + ptags_free(root); +} + +/** + * ptags_cred_prepare - prepare new set of credentials for modification + * @new: the new credentials + * @old: the original credentials + * @gfp: the atomicity of any memory allocations + * + * Prepare a new set of credentials for modification. + */ +static int ptags_cred_prepare(struct cred *new, const struct cred *old, + gfp_t gfp) +{ + int rc; + + rc = ptags_cred_alloc_blank(new, gfp); + if (rc == 0) + rc = ptags_copy(ptags_of_cred(new), ptags_of_cred(old)); + return rc; +} + +/** + * ptags_cred_transfer - Transfer the old credentials to the new credentials + * @new: the new credentials + * @old: the original credentials + * + * Fill in a set of blank credentials from another set of credentials. + */ +static void ptags_cred_transfer(struct cred *new, const struct cred *old) +{ + ptags_move(ptags_of_cred(new), ptags_of_cred(old)); +} + +/** + * ptags_getprocattr - reads the file 'name' of the task 'task' + * @task: the object task + * @name: the name of the attribute in /proc/.../attr + * @value: where to put the result + * + * Reads the ptags + * + * Returns the length read + */ +static int ptags_getprocattr(struct task_struct *task, char *name, char **value) +{ + if (ptags_is_ptags_file(name)) + return ptags_read(ptags_of_task(task), value); + return 0; +} + +/** + * ptags_setprocattr - write the file 'name' of the task 'task + * + * @task: the object task + * @name: the name of the attribute in /proc/.../attr + * @value: the value to set + * @size: the size of the value + * + * Sets ptags + * + * Returns the length writen data + */ +static int ptags_setprocattr(struct task_struct *task, char *name, + void *value, size_t size) +{ + struct ptags *croot; + if (ptags_is_ptags_file(name)) { + if ((current->flags & PF_KTHREAD) || +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + has_ns_capability(task, task_cred_xxx(task, user_ns), CAP_MAC_ADMIN) +#else + has_capability(task, CAP_MAC_ADMIN) +#endif + ) + croot = NULL; + else + croot = ptags_of_current(); + return ptags_write(croot, ptags_of_task(task), value, size); + } + return 0; +} + +/* + * List of hooks + */ +static struct security_hook_list ptags_hooks[] = { + + LSM_HOOK_INIT(bprm_committing_creds, ptags_bprm_committing_creds), + + LSM_HOOK_INIT(cred_alloc_blank, ptags_cred_alloc_blank), + LSM_HOOK_INIT(cred_free, ptags_cred_free), + LSM_HOOK_INIT(cred_prepare, ptags_cred_prepare), + LSM_HOOK_INIT(cred_transfer, ptags_cred_transfer), + + LSM_HOOK_INIT(getprocattr, ptags_getprocattr), + LSM_HOOK_INIT(setprocattr, ptags_setprocattr), +}; + +/** + * ptags_init - initialize the tags system + * + * Returns 0 + */ +static __init int ptags_init(void) +{ + int rc; + + pr_info("PTags: Initialising.\n"); + + /* Set the tags for the initial task. */ + rc = ptags_cred_alloc_blank((struct cred *)current->cred, GFP_KERNEL); + if (rc != 0) + return -ENOMEM; + + /* Register with LSM */ + security_add_hooks(ptags_hooks, ARRAY_SIZE(ptags_hooks)); + + return 0; +} + +/* + * Smack requires early initialization in order to label + * all processes and objects when they are created. + */ +security_initcall(ptags_init); diff --git a/security/ptags/ptags.c b/security/ptags/ptags.c new file mode 100644 index 0000000..75b4523 --- /dev/null +++ b/security/ptags/ptags.c @@ -0,0 +1,2377 @@ +/* + * Copyright (C) 2016 José Bollo + * + * 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. + * + * Author: + * José Bollo + */ + +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS +# define IF_NS(x) x +# define IF_NO_NS(x) +#else +# define IF_NS(x) +# define IF_NO_NS(x) x +#endif + +/* + * Definition of characters + */ +#define ADD_CHAR '+' /* add tag or keep flag */ +#define SUB_CHAR '-' /* sub tag or keep flag */ +#define SET_CHAR '!' /* set a value to a tag */ +#define COMMENT_CHAR '#' /* comment */ +#define QUERY_CHAR '?' /* query */ + +#define KEEP_CHAR '@' /* keep char */ +#define ASSIGN_CHAR '=' /* key/value separator */ +#define SEPAR_CHAR ':' /* field delimitors */ +#define GLOB_CHAR '*' /* global pattern (at end) */ + +#define EOL_CHAR '\n' /* End Of Line */ + +/* + * Maximum count of ptags + */ +#define MAXCOUNT 4000 + +/* + * Maximum length of a tag + */ +#define MAXTAGLEN 4000 + +/* + * Maximum length of a value + */ +#define MAXVALUELEN 32700 + +/* + * Increment size + */ +#define CAPACITYINCR 100 + +/* + * static strings + */ +static const char prefix_string[] = "ptags:"; +static const char add_string[] = "add"; +static const char sub_string[] = "sub"; +static const char set_string[] = "set"; +static const char others_string[] = "others"; + +/* + * length of static strings + */ +#define prefix_string_length ((int)((sizeof prefix_string) - 1)) +#define add_string_length ((int)((sizeof add_string) - 1)) +#define sub_string_length ((int)((sizeof sub_string) - 1)) +#define set_string_length ((int)((sizeof set_string) - 1)) +#define others_string_length ((int)((sizeof others_string) - 1)) + +/* + * slice of entry index + */ +struct slice { + unsigned lower; /* lower index of the slice */ + unsigned upper; /* upper index of the slice plus one */ +}; + +/* + * items are reference counted strings + */ +struct item { + atomic_t refcount; /* reference count of the string */ + unsigned length; /* length of the string (without terminal zero) */ + char value[1]; /* the zero terminated string */ +}; + +/* + * value records the value and the state of tags + */ +struct value { + uintptr_t data; + /* pointer to the value with used lower bits */ + /* bit 0: kept flag */ + /* bit 1: removed flag */ +}; + +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS +/* + * Specific data for user namespace + */ +#define HINT_COUNT 3 /* cache count for hints */ +#define HINT_NONE -1 /* not a parent */ +#define NSCAPACITYINCR 10 /* capacity increment for nsrefs */ + +/* + * hints record how namespaces are linked + */ +struct nshint { + struct user_namespace *target; /* target user namespace */ + int hint; /* the hint: level of ancestry */ +}; +/* + * weak reference to user namespace with cache + */ +struct nsref { + struct user_namespace *userns; /* the user namespace */ + struct nshint hints[HINT_COUNT]; /* cache of hints */ +}; + +/* + * value for multi user namespace + */ +struct nsval { + struct nsval *next; /* next nsval if any */ + struct nsref *nsref; /* namespace reference */ + struct value value; /* the value */ +}; +#endif + +/* + * entries record ptags, their value and kept flag + */ +struct entry { + struct item *name; /* the item for the name */ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsval *first; /* first value of the entry */ +#else + struct value value; /* the value of the entry */ +#endif +}; + +/* + * ptags internal data + */ +struct _ptags { + struct entry *entries; /* array of entries */ + unsigned count; /* count of entries */ + unsigned capacity; /* allocated count of entries */ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsref *nsrefs; /* references to user namespaces */ + unsigned nsrefs_count; /* count of user namespaces */ + unsigned nsrefs_capacity; /* allocated count of user namespaces */ + int wantgc; /* is garbage collection expected ? */ +#endif +}; + +/* + * ptags data attached to tasks + */ +struct ptags { + struct mutex lock; /* mutex access */ + struct _ptags data; /* internal data */ +}; + +/* + * user namespace proxy + */ +struct uns { +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct user_namespace *userns; /* the user namespace */ +#endif +}; + +/******************************************************************* + * section: validity + ******************************************************************/ + +/** + * is_valid_utf8 - Is buffer a valid utf8 string? + * + * @buffer: the start of the string + * @length: length in bytes of the buffer + * + * Return 1 when valid or else returns 0 + */ +static int is_valid_utf8(const char *buffer, unsigned length) +{ + unsigned x; + while (length) { + /* check the first character */ + x = (unsigned)(unsigned char)*buffer++; + if (x <= 127) + x = 1; + else if ((x & 0xc0) == 0x80) + return 0; + else if ((x & 0xe0) == 0xc0) { + if ((x & 0xfe) == 0xc0) + return 0; + x = 2; + } else if ((x & 0xf0) == 0xe0) + x = 3; + else if ((x & 0xf8) == 0xf0) + x = 4; + else if ((x & 0xfc) == 0xf8) + x = 5; + else if ((x & 0xfe) == 0xfc) + x = 6; + else + return 0; + + /* check the length */ + if (length < x) + return 0; + length -= x; + + /* check the remaining characters */ + while (--x) { + if ((*buffer++ & '\xc0') != '\x80') + return 0; + } + } + return 1; +} + +/** + * is_valid_base - Is buffer a valid base for tag or prefix? + * + * @buffer: string for the tag or prefix name + * @length: length in bytes of the buffer + * + * Return 1 when valid or else returns 0 + */ +static int is_valid_base(const char *buffer, unsigned length) +{ + unsigned i; + char c; + + /* should not start with KEEP_CHAR */ + if (length <= 0 || length > MAXTAGLEN || buffer[0] == KEEP_CHAR) + return 0; + + /* should not contain ASSIGN_CHAR or GLOB_CHAR */ + for (i = 0; i < length; i++) { + c = buffer[i]; + if (c < ' ' || c == '\x7f' || c == ASSIGN_CHAR + || c == GLOB_CHAR) + return 0; + } + return 1; +} + +/** + * is_valid_tag - Is buffer a valid tag? + * + * @buffer: string for the tag name + * @length: length in bytes of the buffer + * + * Return 1 when valid or else returns 0 + */ +static int is_valid_tag(const char *buffer, unsigned length) +{ + unsigned i; + + /* exclude bad tags */ + if (!is_valid_base(buffer, length) + || buffer[length - 1] == SEPAR_CHAR) + return 0; + + /* accept common tags */ + if (length <= prefix_string_length + || memcmp(buffer, prefix_string, prefix_string_length)) + return 1; + + /* + * The tag is "ptags:...." + * Get the last part + */ + i = length; + while (buffer[i - 1] != SEPAR_CHAR) + i = i - 1; + length -= i; + buffer += i; + + /* + * Check the last part + */ + return (length == add_string_length + && !memcmp(add_string, buffer, length)) + || (length == sub_string_length + && !memcmp(sub_string, buffer, length)) + || (length == set_string_length + && !memcmp(set_string, buffer, length)) + || (length == others_string_length + && !memcmp(others_string, buffer, length)); +} + +/** + * is_valid_prefix - Is buffer a valid prefix? + * + * @buffer: string for the tag name + * @length: length in bytes of the buffer + * + * Return 1 when valid or else returns 0 + */ +static inline int is_valid_prefix(const char *buffer, unsigned length) +{ + return is_valid_base(buffer, length) + && buffer[length - 1] == SEPAR_CHAR; +} + +/** + * is_valid_value - Is buffer a valid value? + * + * @buffer: string for the tag name + * @length: length in bytes of the buffer + * + * Return 1 when valid or else returns 0 + */ +static int is_valid_value(const char *buffer, unsigned length) +{ + unsigned i; + char c; + if (length > MAXVALUELEN) + return 0; + for (i = 0; i < length; i++) { + c = buffer[i]; + if (c < ' ' || c == '\x7f') + return 0; + } + return 1; +} + +/******************************************************************* + * section: item + * + * The structure item handles one string that is used either + * for storing the tags or their values. + * This structure is reference counted. + ******************************************************************/ + +/* + * item_create - Creates an item for the 'value' of 'length' + * + * @value: the value copied as value of the item + * @length: length in bytes of the value + * + * Returns the create item or NULL on error. + */ +static struct item *item_create(const char *value, unsigned length) +{ + struct item *item; + + item = kmalloc(length + sizeof *item, GFP_KERNEL); + if (item) { + atomic_set(&item->refcount, 1); + item->length = length; + memcpy(item->value, value, length); + item->value[length] = 0; + } + return item; +} + +/* + * item_addref - Adds a reference to 'item' and returns it + * + * @item: the item to use (must not be NULL) + * + * Returns the item + */ +static inline struct item *item_addref(struct item *item) +{ + atomic_inc(&item->refcount); + return item; +} + +/* + * item_addref_safe - Adds a reference to 'item' and returns it + * + * @item: the item to use (can be NULL) + * + * Returns the item + */ +static inline struct item *item_addref_safe(struct item *item) +{ + return item ? item_addref(item) : item; +} + +/* + * item_unref - Removes a reference to 'item' + * + * @item: the item whose reference count is to be decremented + * + * 'item' must not be NULL. It is destroyed when no more used. + */ +static inline void item_unref(struct item *item) +{ + if (atomic_dec_and_test(&item->refcount)) + kfree(item); +} + +/* + * item_unref - Removes a reference to 'item' + * + * @item: the item whose reference count is to be decremented + * + * 'item' can be NULL. It is destroyed when no more used. + */ +static inline void item_unref_safe(struct item *item) +{ + if (item) + item_unref(item); +} + +/* + * item_has_prefix - Has 'item' the 'prefix' of 'length'? + * + * @item: the item to test + * @prefix: the prefix to search + * @length: the length in byte of the prefix + * + * Returns 1 if 'item' has the 'prefix' or else returns 0 + */ +static inline int item_has_prefix(const struct item *item, const char *prefix, + unsigned length) +{ + return item->length >= length && !memcmp(item->value, prefix, length); +} + +/******************************************************************* + * section: value + * + * A value records the following data: + * - value: an item being the value attached to the tag (can be NULL) + * - kept: a boolean flag indicating if the tag is kept accross execve + * - removed: a boolean flag indicating if the value was removed + * + * Note: for limiting memory usage, boolean flags are taken in the + * lower bits of the pointer to the value + ******************************************************************/ + +/* + * value_get - Gets the value of 'value' + * + * @value: the value whose value is to get + * + * Returns the value that can be NULL of 'value' + */ +static inline struct item *value_get(struct value value) +{ + return (struct item *)(value.data & ~(uintptr_t) 3); +} + +/* + * value_set - Sets the value of 'value' to 'value' + * + * @value: the value whose value is to set + * @item: the item to set (can be NULL) + */ +static inline void value_set(struct value *value, struct item *item) +{ + item_unref_safe(value_get(*value)); + value->data = (uintptr_t) item | (value->data & (uintptr_t) 1); +} + +/* + * value_is_removed - Is the 'value' removed? + * + * @value: the value to test + * + * Returns 1 if value is removed or 0 else + */ +static inline int value_is_removed(struct value value) +{ + return (int)(value.data & (uintptr_t) 2); +} + +/* + * value_set_removed - Sets the 'value' as removed + * + * @value: the value to set + */ +static inline void value_set_removed(struct value *value) +{ + item_unref_safe(value_get(*value)); + value->data = (value->data & (uintptr_t) 1) | (uintptr_t) 2; +} + +/* + * value_is_kept - Is the 'value' to be kept? + * + * @value: the value to test + * + * Returns 1 if value is kept or 0 else + */ +static inline int value_is_kept(struct value value) +{ + return (int)(value.data & (uintptr_t) 1); +} + +/* + * value_set_kept - Sets the kept flag of the 'value' + * + * @value: the value to set + */ +static inline void value_set_kept(struct value *value) +{ + value->data |= (uintptr_t) 1; +} + +/* + * value_clear_kept - Clears the kept flag of the 'value' + * + * @value: the value to clear + */ +static inline void value_clear_kept(struct value *value) +{ + value->data &= ~(uintptr_t) 1; +} + +/* + * value_make - Makes a new value + * + * Returns a just existing value + */ +static inline struct value value_make(void) +{ + struct value result; + result.data = 0; + return result; +} + +/* + * value_clone - Clones the 'value' + * + * @value: the value to clone + * + * Returns an value with same data than 'value' but whose reference + * counts are incremented. + */ +static inline struct value value_clone(struct value value) +{ + struct value result; + result.data = value.data; + item_addref_safe(value_get(result)); + return result; +} + +/* + * value_erase - Erases the content of 'value' + * + * @value: the value to erase + * + * The name and value of the value are dereferenced + */ +static inline void value_erase(struct value value) +{ + item_unref_safe(value_get(value)); +} + +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS +/******************************************************************* + * section: nsref + ******************************************************************/ + +/* + * nsref_init - Initialise 'nsref' to reference 'userns' + * + * @nsref: the nsref to initialize + * @userns: the user namespace to reference + */ +static void nsref_init(struct nsref *nsref, struct user_namespace *userns) +{ + struct nshint *hcur, *hend; + + /* get a weak reference to the user namespace */ + nsref->userns = get_weak_user_ns(userns); + + /* clears hint cache */ + hcur = nsref->hints; + hend = &hcur[HINT_COUNT]; + while (hcur != hend) { + hcur->target = NULL; + hcur++; + } +} + +/* + * nsref_erase_hints - Erase hints data of 'nsref' + * + * @nsref: the reference to erase. + */ +static void nsref_erase_hints(struct nsref *nsref) +{ + struct nshint *hcur, *hend; + + /* unref hints */ + hcur = nsref->hints; + hend = &hcur[HINT_COUNT]; + while (hcur != hend && hcur->target != NULL) { + put_weak_user_ns(hcur->target); + hcur++; + } +} + +/* + * nsref_erase - Erase data of 'nsref' + * + * @nsref: the reference to erase. + */ +static inline void nsref_erase(struct nsref *nsref) +{ + nsref_erase_hints(nsref); + put_weak_user_ns(nsref->userns); +} + +/* + * nsref_remove_ghost_hints - Removes hint to ghost namespaces + * + * @nsref: the reference to clean + */ +static void nsref_remove_ghost_hints(struct nsref *nsref) +{ + struct user_namespace *userns; + struct nshint *hcur, *hend, *hto; + + hcur = nsref->hints; + hto = hcur; + hend = &hcur[HINT_COUNT]; + while (hcur != hend && (userns = hcur->target) != NULL) { + if (!is_weak_user_ns_still_alive(userns)) + put_weak_user_ns(userns); + else { + if (hto != hcur) + *hto = *hcur; + hto++; + } + hcur++; + } + while (hto != hcur) + hto++->target = NULL; +} + +/* + * nsref_userns_hint - Get the hint for 'userns' + * + * The hint is the level of ancestry of 'userns' within context + * of 'nsref'. If 'nsref' references 'userns', the result if 0. + * If 'userns' is a parent of the namespace referenced by 'nsref' + * it returns a positive integer being the level of ancestry. + * Otherwise a negative value is returned (HINT_NONE). + * + * @nsref: the reference to the usernamespace + * @userns: the usernamespace queried + * + * Returns the hint. + */ +static int nsref_userns_hint(struct nsref *nsref, struct user_namespace *userns) +{ + struct nshint h0, h1, *hcur, *hend; + struct user_namespace *it, *ref; + + /* 0 if equal to reference */ + ref = nsref->userns; + if (userns == ref) + return 0; + + /* search cached hint and reorder lru */ + hcur = nsref->hints; + hend = &hcur[HINT_COUNT]; + h0 = *hcur; + if (h0.target == userns) + return h0.hint; /* no reorder needed */ + while (h0.target) { + if (++hcur == hend) { + put_weak_user_ns(h0.target); + break; + } + h1 = *hcur; + *hcur = h0; + if (h1.target == userns) { + nsref->hints[0] = h1; + return h1.hint; + } + if (!h1.target) + break; + if (++hcur == hend) { + put_weak_user_ns(h1.target); + break; + } + h0 = *hcur; + *hcur = h1; + if (h0.target == userns) { + nsref->hints[0] = h0; + return h0.hint; + } + } + + /* compute the hint */ + h0.target = get_weak_user_ns(userns); + h0.hint = 1; + it = userns->parent; + for (;;) { + if (!it) { + h0.hint = HINT_NONE; + break; + } + if (it == ref) + break; + h0.hint++; + it = it->parent; + } + + /* record and end */ + nsref->hints[0] = h0; + return h0.hint; +} +#endif +/******************************************************************* + * section: entry + * + * An entry records the following data: + * - name: an item being the tag name + * - value: an item being the value attached to the tag (can be NULL) + * - kept: a boolean flag indicating if the tag is kept accross execve + * - removed: a boolean flag indicating if the entry was removed + * + * Note: for limiting memory usage, bollean flags are taken in the + * lower bits of the pointer to the value + ******************************************************************/ + +/* + * entry_name - Gets the name of 'entry' + * + * @entry: the entry whose name is to get + * + * Returns the name of 'entry' + */ +static inline struct item *entry_name(struct entry entry) +{ + return entry.name; +} + +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS +/* + * entry_nsval - Returns the existing readable nsval of 'entry' for 'uns' + * + * @entry: the entry to search in + * @uns: the user namespace proxy + * + * Returns the nsval of entry having the best hint or NULL if none exist. + */ +static struct nsval *entry_nsval(struct entry *entry, struct uns uns) +{ + struct nsval *val, *r; + int h, rh; + + val = entry->first; + while(val) { + h = nsref_userns_hint(val->nsref, uns.userns); + if (!h) + return val; + if (h > 0) { + rh = h; + r = val->next; + while(r) { + h = nsref_userns_hint(r->nsref, uns.userns); + if (!h) + return r; + if (h > 0 && h < rh) { + rh = h; + val = r; + } + r = r->next; + } + return val; + } + val = val->next; + } + return NULL; +} +#endif + +/* + * entry_read - Get the read value of 'entry' for 'uns' + * + * @entry: the entry to search in + * @uns: the user namespace proxy + * + * Returns the address of the value to read or NULL if the entry doean't + * exist or is removed. + */ +static inline struct value *entry_read(struct entry *entry, struct uns uns) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsval *r; + + r = entry_nsval(entry, uns); + return r && !value_is_removed(r->value) ? &r->value : NULL; +#else + return value_is_removed(entry->value) ? NULL : &entry->value; +#endif +} + +/* + * entry_make - Makes a new entry + * + * @name: name of the entry + * + * Returns the entry initialized with the given values + */ +static inline struct entry entry_make(struct item *name) +{ + struct entry result; + result.name = name; +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + result.first = NULL; +#else + result.value = value_make(); +#endif + return result; +} + +/* + * entry_erase - Erases the content of 'entry' + * + * @entry: the entry to erase + * + * The name and value of the entry are dereferenced + */ +static inline void entry_erase(struct entry entry) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsval *val; + + while((val = entry.first) != NULL) { + entry.first = val->next; + value_erase(val->value); + kfree(val); + } +#else + value_erase(entry.value); +#endif + item_unref(entry_name(entry)); +} + +/* + * entry_is_removed - Is the entry to be removed? + * + * @entry: the entry to test + * + * Returns 1 if the entry can be removed. + */ +static inline int entry_is_removed(struct entry entry) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsval *val; + + /* note: an other politic, here, would be to return 0 always */ + val = entry.first; + while(val) { + if (!value_is_removed(val->value)) + return 0; + val = val->next; + } + return 1; +#else + return value_is_removed(entry.value); +#endif +} + +/* + * entry_prune - Removes the values that are not to be kept + * + * @entry: the entry to prune + * + * Return 1 if the entry is to be kept or 0 if it is removed + * and erased (erase is not to be called) + */ +static IF_NO_NS(inline) int entry_prune(struct entry *entry) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsval *val, **prv; + int remove; + + /* effective prune */ + remove = 1; + prv = &entry->first; + val = entry->first; + while(val != NULL) { + if (value_is_kept(val->value)) { + if (!value_is_removed(val->value)) + remove = 0; + prv = &val->next; + val = val->next; + } else { + *prv = val->next; + value_erase(val->value); + kfree(val); + val = *prv; + } + } + /* test if empty */ + prv = &entry->first; + val = *prv; + if (val && !remove) + return 1; + /* clean up */ + while(val != NULL) { + *prv = val->next; + value_erase(val->value); + kfree(val); + val = *prv; + } +#else + if (value_is_kept(entry->value)) + return 1; + value_erase(entry->value); +#endif + item_unref(entry_name(*entry)); + return 0; +} + +/******************************************************************* + * section: entries + ******************************************************************/ + +/* + * entries_search - Searchs the 'name' of 'length' in the entries + * + * @entries: array of entries to be searched + * @count: count of entries in 'entries' + * @name: name to search + * @length: length in bytes of the searched name + * @glob: boolean indicating if search is global (on prefix) + * + * Returns the slice found. The entries found are at indexes from result.lower + * to result.upper-1. When nothing is found, the insert index is returned + * in result.lower and result.upper. + * + * This function asserts that entries is ordered. + */ +static struct slice entries_search(struct entry *entries, unsigned count, + const char *name, unsigned length, int glob) +{ + int cmp; + unsigned idx; + struct slice r; + struct item *item; + + /* dichotomic search */ + r.lower = 0; + r.upper = count; + while (r.lower != r.upper) { + idx = (r.lower + r.upper) >> 1; + item = entry_name(entries[idx]); + if (length > item->length) { + cmp = memcmp(item->value, name, item->length); + if (cmp <= 0) + r.lower = idx + 1; + else + r.upper = idx; + } else { + cmp = memcmp(item->value, name, length); + if (!cmp && (glob || length == item->length)) { + r.lower = idx; + r.upper = idx + 1; + if (glob) + goto extend; + goto end; + } + if (cmp < 0) + r.lower = idx + 1; + else + r.upper = idx; + } + } + goto end; + extend: + /* extend selection (if glob) */ + while (r.lower > 0 + && item_has_prefix(entry_name(entries[r.lower - 1]), name, + length)) + r.lower--; + while (r.upper < count + && item_has_prefix(entry_name(entries[r.upper]), name, length)) + r.upper++; + end: + return r; +} + +/******************************************************************* + * section: _ptags + ******************************************************************/ + +/* + * _ptags_query_gc - Request a garbage collection for ptags + * + * @ptags: the ptags to be cleaned + */ +static inline void _ptags_query_gc(struct _ptags *ptags) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + ptags->wantgc = 1; +#endif +} + +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS +/* + * _ptags_erase_tagged_nsref - remove tagged namespace references + * + * @ptags: ptags to be cleaned + */ +static void _ptags_erase_tagged_nsref(struct _ptags *ptags) +{ + struct nsref *nscur, *nsend; + struct entry *entcur, *entend, *entto; + struct nsval *curval, **preval; + + /* clean ghost references references */ + nscur = ptags->nsrefs; + nsend = &nscur[ptags->nsrefs_count]; + while (nscur != nsend) { + if (nscur->userns) { + /* still alive, skip */ + nscur++; + } else { + /* removes the current nsref */ + nsref_erase_hints(nscur); + if (--nsend != nscur) + *nscur = *nsend; + + /* update entries */ + entcur = ptags->entries; + entto = entcur; + entend = &entcur[ptags->count]; + while (entcur != entend) { + preval = &entcur->first; + curval = entcur->first; + while (curval) { + if (curval->nsref == nscur) { + /* + * note: it is asserted that parent + * namespaces die after children. + */ + *preval = curval->next; + value_erase(curval->value); + kfree(curval); + curval = *preval; + } else { + if (curval->nsref == nsend) + curval->nsref = nscur; + preval = &curval->next; + curval = curval->next; + } + } + if (entry_is_removed(*entcur)) + entry_erase(*entcur); + else { + if (entto != entcur) + *entto = *entcur; + entto++; + } + entcur++; + } + ptags->count = (unsigned)(entto - ptags->entries); + } + } + /* clean ghost hints */ + nscur = ptags->nsrefs; + ptags->nsrefs_count = (unsigned)(nsend - nscur); + while (nscur != nsend) { + nsref_remove_ghost_hints(nscur); + nscur++; + } +} + +/* + * _ptags_collect_garbage - remove unused namespace references + * + * @ptags: ptags to be cleaned + */ +static void _ptags_collect_garbage(struct _ptags *ptags) +{ + struct user_namespace *userns; + struct nsref *nscur, *nsend; + struct entry *entcur, *entend; + struct nsval *val; + int changed; + +return; + changed = 0; + + /* scan entries entries */ + entcur = ptags->entries; + entend = &entcur[ptags->count]; + while (entcur != entend) { + val = entcur->first; + while (val) { + nscur = val->nsref; + userns = nscur->userns; + nscur->userns = (void*)((uintptr_t) userns | (uintptr_t)1); + val = val->next; + } + entcur++; + } + + /* detect leaks */ + nscur = ptags->nsrefs; + nsend = &nscur[ptags->nsrefs_count]; + while (nscur != nsend) { + userns = nscur->userns; + if (((uintptr_t)1) & ((uintptr_t)userns)) + nscur->userns = (void*)((uintptr_t) userns & ~(uintptr_t)1); + else { + nscur->userns = NULL; + put_weak_user_ns(userns); + changed = 1; + } + nscur++; + } + if (changed) + _ptags_erase_tagged_nsref(ptags); + + ptags->wantgc = 0; +} + +/* + * _ptags_clean_nsrefs - Removes dependencies to ghost namespaces of 'ptags' + * + * @ptags: ptags to be cleaned + */ +static void _ptags_clean_nsrefs(struct _ptags *ptags) +{ + struct user_namespace *userns; + struct nsref *nscur, *nsend; + int changed; + + for(;;) { + changed = 0; + + /* clean ghost references references */ + nscur = ptags->nsrefs; + nsend = &nscur[ptags->nsrefs_count]; + while (nscur != nsend) { + userns = nscur->userns; + if (!is_weak_user_ns_still_alive(userns)) { + nscur->userns = NULL; + put_weak_user_ns(userns); + changed = 1; + } + nscur++; + } + if (!changed) + break; + _ptags_erase_tagged_nsref(ptags); + } +} + +/* + * _ptags_nsref - Get/create within 'ptags' the namespace reference for 'uns' + * + * @ptags: where is the reference + * @uns: the user namespace proxy + * + * Return the reference to the user namespace proxied by 'uns' within 'ptags' + */ +static struct nsref *_ptags_nsref(struct _ptags *ptags, struct uns uns) +{ + struct nsref *nsorg, *nsref, *nsend; + struct entry *ecur, *eend; + struct nsval *nsval; + unsigned count; + + /* search existing */ + nsorg = ptags->nsrefs; + nsref = nsorg; + count = ptags->nsrefs_count; + nsend = &nsorg[count]; + while (nsref != nsend) { + if (nsref->userns == uns.userns) + return nsref; /* found */ + nsref++; + } + + /* create one */ + if (count == ptags->nsrefs_capacity) { + /* increase the size */ + nsref = krealloc(nsorg, (count + NSCAPACITYINCR) * sizeof *nsref, GFP_KERNEL); + if (nsref == NULL) + return NULL; + + /* rellocation if needed */ + if (nsref != nsorg) { + ecur = ptags->entries; + eend = &ecur[ptags->count]; + while (ecur != eend) { + nsval = ecur++->first; + while(nsval) { + nsval->nsref = &nsref[nsval->nsref - nsorg]; + nsval = nsval->next; + } + } + } + + /* records data */ + ptags->nsrefs = nsref; + ptags->nsrefs_capacity = count + NSCAPACITYINCR; + nsref += count; + } + ptags->nsrefs_count = count + 1; + nsref_init(nsref, uns.userns); + return nsref; +} +#endif + +/* + * _ptags_entry_write - Gets the write value of existing 'entry' for 'uns' within 'ptags' + * + * @ptags: the ptags where the value is to be written + * @entry: the entry where the value will be written + * @uns: proxy to user namespace + * + * Returns the pointer to the value to write or NULL on memory depletion. + */ +static IF_NO_NS(inline) int _ptags_entry_write(struct _ptags *ptags, struct entry *entry, struct uns uns, struct value **to) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsval *rval, *val; + struct nsref *nsref; + + rval = entry_nsval(entry, uns); + if (!rval || value_is_removed(rval->value)) + return 0; + + nsref = _ptags_nsref(ptags, uns); + if (nsref == NULL) + return -1; + + if (rval && rval->nsref == nsref) + *to = &rval->value; + else { + val = kmalloc(sizeof *val, GFP_KERNEL); + if (!val) + return -1; + + val->value = rval ? value_clone(rval->value) : value_make(); + val->nsref = nsref; + val->next = entry->first; + entry->first = val; + *to = &val->value; + } +#else + *to = &entry->value; +#endif + return 1; +} + +/* + * _ptags_entry_create - Creates the write value of 'entry' for 'uns' within 'ptags' + * + * @ptags: the ptags where the value is to be written + * @entry: the entry where the value will be written + * @uns: proxy to user namespace + * + * Returns the pointer to the value to write or NULL on memory depletion. + */ +static IF_NO_NS(inline) struct value *_ptags_entry_create(struct _ptags *ptags, struct entry *entry, struct uns uns) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsval *rval, *val; + struct nsref *nsref; + + /* get the namespace reference for uns */ + nsref = _ptags_nsref(ptags, uns); + if (nsref == NULL) + return NULL; + + /* search the read nsval */ + rval = entry_nsval(entry, uns); + if (rval) { + if (rval->nsref == nsref) { + if (value_is_removed(rval->value)) + rval->value = value_make(); + return &rval->value; + } + if (value_is_removed(rval->value)) + rval = NULL; + } + + /* creates the nsval */ + val = kmalloc(sizeof *val, GFP_KERNEL); + if (!val) + return NULL; + + /* init it */ + val->value = rval ? value_clone(rval->value) : value_make(); + val->nsref = nsref; + val->next = entry->first; + entry->first = val; + return &val->value; +#else + return &entry->value; +#endif +} + +/* + * _ptags_erase - Erases the content of 'ptags' + * + * @ptags: the ptags whose content is to erase (must not be NULL) + */ +static void _ptags_erase(struct _ptags *ptags) +{ + struct entry *entries; + unsigned count; +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsref *nsrefs; +#endif + + count = ptags->count; + entries = ptags->entries; + while (count) + entry_erase(entries[--count]); + kfree(entries); +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + count = ptags->nsrefs_count; + nsrefs = ptags->nsrefs; + while (count) + nsref_erase(&nsrefs[--count]); + kfree(nsrefs); +#endif +} + +/** + * _ptags_prune - Prunes from 'ptags' the entries not kept + * + * @ptags: the ptags to be puned + */ +static void _ptags_prune(struct _ptags *ptags) +{ + unsigned i, j, count; + struct entry *entries; + + entries = ptags->entries; + count = ptags->count; + for (i = j = 0; i < count; i++) { + if (entry_prune(&entries[i])) + entries[j++] = entries[i]; + } + ptags->count = j; +} + +/** + * _ptags_copy - Copies entries from locked 'src' to locked 'dst' + * + * @dst: locked destination ptags + * @src: locked source ptags + * + * Returns 0 on success or -ENOMEM on memory allocation failure. + */ +static int _ptags_copy(struct _ptags *dst, struct _ptags *src) +{ + struct _ptags tmp; + unsigned i; + struct entry *from, e, x; +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + struct nsref *fromrefs, *nscur, *nsend; + struct nshint *hcur, *hend; + struct nsval *val, **toval, *itval; +#endif + + /* allocates the entries */ + tmp.count = src->count; + tmp.entries = kmalloc(tmp.count * sizeof *tmp.entries, GFP_KERNEL); + if (!tmp.entries) + return -ENOMEM; + tmp.capacity = tmp.count; + +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + /* allocates the namespace references */ + tmp.nsrefs_count = src->nsrefs_count; + tmp.nsrefs = kmalloc(tmp.nsrefs_count * sizeof *tmp.nsrefs, GFP_KERNEL); + if (!tmp.nsrefs) { + kfree(tmp.entries); + return -ENOMEM; + } + tmp.nsrefs_capacity = tmp.nsrefs_count; + + /* copy the namespace references */ + fromrefs = src->nsrefs; + memcpy(tmp.nsrefs, fromrefs, tmp.nsrefs_count * sizeof *tmp.nsrefs); + nscur = tmp.nsrefs; + nsend = &nscur[tmp.nsrefs_count]; + while (nscur != nsend) { + get_weak_user_ns(nscur->userns); + hcur = nscur++->hints; + hend = &hcur[HINT_COUNT]; + while(hcur != hend && hcur->target) + get_weak_user_ns(hcur++->target); + } +#endif + /* copy the entries */ + from = src->entries; + for (i = 0; i < tmp.count; i++) { + x = from[i]; + e.name = item_addref(entry_name(x)); +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + toval = &e.first; + itval = x.first; + while (itval) { + val = kmalloc(sizeof *val, GFP_KERNEL); + *toval = val; + if (val == NULL) { + /* out of memory: cleanup */ + entry_erase(e); + tmp.count = i; + _ptags_erase(&tmp); + return -ENOMEM; + } + val->nsref = &tmp.nsrefs[itval->nsref - fromrefs]; + val->value = value_clone(itval->value); + toval = &val->next; + itval = itval->next; + } + *toval = NULL; +#else + e.value = value_clone(x.value); +#endif + tmp.entries[i] = e; + } + + /* assign the copy */ + _ptags_erase(dst); +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + tmp.wantgc = 0; +#endif + *dst = tmp; + + return 0; +} + +/* + * _ptags_init - Creates and initializes the ptags structure + * + * Returns the init ptags. + */ +static inline void _ptags_init(struct _ptags *ptags) +{ + ptags->entries = NULL; + ptags->count = 0; + ptags->capacity = 0; +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + ptags->nsrefs = NULL; + ptags->nsrefs_count = 0; + ptags->nsrefs_capacity = 0; + ptags->wantgc = 0; +#endif +} + +/** + * _ptags_move - Transfers entries from 'src' to 'dst' + * + * @dst: destination ptags + * @src: source ptags + */ +static inline void _ptags_move(struct _ptags *dst, struct _ptags *src) +{ + _ptags_erase(dst); + *dst = *src; + _ptags_init(src); +} + +/******************************************************************* + * section: uns + ******************************************************************/ +/* + * uns_get - Get the current user namespace proxy + */ +static inline struct uns uns_get(void) +{ + struct uns uns; +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + uns.userns = get_user_ns(current_user_ns()); +#endif + return uns; +} + +/* + * uns_put - release the proxy + * + * @uns: the proxy to release + */ +static inline void uns_put(struct uns uns) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + put_user_ns(uns.userns); +#endif +} + +/******************************************************************* + * section: ptags check + * + * This checks the validity of requested actions + ******************************************************************/ + +/* + * check_action - Checks if the action 'astr' of length 'alen' is + * authorized for the tag 'tstr' of length 'tlen' + * + * @ptags: the ptags controling the action (ptags of current) + * @tstr: the tags string to modify + * @tlen: the length of the tag + * @astr: the action sting to check + * @alen: the length of the action + * @uns: user namespace proxy + * + * Returns 1 if the action is authorized or 0 if not + */ +static int check_action(struct _ptags *ptags, const char *tstr, unsigned tlen, + const char *astr, unsigned alen, struct uns uns) +{ + unsigned i, ilen; + struct slice slice; + struct entry *entries; + struct item *item; + char *istr; + + /* Searchs the entries "ptags:...." */ + entries = ptags->entries; + slice = entries_search(entries, ptags->count, prefix_string, + prefix_string_length, 1); + + /* Loop on found entries */ + for (i = slice.lower; i < slice.upper; i++) { + + /* must be available for uns */ + if (!entry_read(&entries[i], uns)) + continue; + + /* get the unprefixed entry in istr and ilen */ + item = entry_name(entries[i]); + ilen = item->length - prefix_string_length; + istr = item->value + prefix_string_length; + + if (ilen == alen) { + /* + * case of ptags:action + * + * Accept when action is the searched action 'astr' + * and when tstr hasn't prefix "ptags:" + */ + if (!memcmp(&istr[ilen - alen], astr, alen) && + (tlen < prefix_string_length || + memcmp(tstr, prefix_string, prefix_string_length))) + return 1; + } else if (ilen > alen) { + /* + * case of ptags:prefix:action + * + * Accept when action is the searched action 'astr' + * and either 'tstr' has prefix "prefix:" + * or 'tstr' == prefix + */ + ilen = ilen - alen - 1; + if (istr[ilen] == SEPAR_CHAR + && !memcmp(&istr[ilen + 1], astr, alen)) { + /* searched action found */ + if (tlen > ilen) { + if (!memcmp(istr, tstr, ilen + 1)) + return 1; + } else if (tlen == ilen) { + if (!memcmp(istr, tstr, tlen)) + return 1; + } + } + } + } + + /* not authorized */ + return 0; +} + +/* + * check_tag - Checks if the action 'action' of length 'alen' is + * authorized for the 'tag' of length 'length' + * + * @cptags: the ptags controling the action (ptags of current) + * @mptags: the ptags modified + * @tag: the tag name to modify + * @length: the length of the tag + * @action: the action sting to check + * @alen: the length of the action + * @uns: user namespace proxy + * + * Returns 1 if the action is authorized or 0 if not + */ +static inline int check_tag(struct _ptags *cptags, struct _ptags *mptags, + const char *tag, unsigned length, + const char *action, unsigned alen, struct uns uns) +{ + /* current ptags == NULL means "super capable" */ + if (!cptags) + return 1; + + /* check if the action is forbidden */ + if (!check_action(cptags, tag, length, action, alen, uns)) + return 0; + + /* the action is authorized if current ptags == modified ptags */ + if (cptags == mptags) + return 1; + + /* not the same process/thread, check "others" authorisation */ + return check_action(cptags, tag, length, others_string, + others_string_length, uns); +} + +/* + * check_tag - Checks if the action 'action' of length 'alen' is + * authorized for the 'entry' + * + * @cptags: the ptags controling the action (ptags of current) + * @mptags: the ptags modified + * @entry: the entry to modify + * @action: the action sting to check + * @alen: the length of the action + * @uns: user namespace proxy + * + * Returns 1 if the action is authorized or 0 if not + */ +static inline int check_entry(struct _ptags *cptags, struct _ptags *mptags, + struct entry *entry, const char *action, + unsigned alen, struct uns uns) +{ + struct item *name = entry_name(*entry); + return check_tag(cptags, mptags, name->value, name->length, action, + alen, uns); +} + +/******************************************************************* + * section: ptags operations + ******************************************************************/ + +/** + * _ptags_query - Queries existing of tags + * + * @ptags: queried ptags + * @name: string for the name of the tag + * @length: length in bytes of the tag's name + * @uns: user namespace proxy + * + * Returns 0 in case of success tag present or -ENOENT if the tag is not found + * or invalid. + */ +static int _ptags_query(struct _ptags *ptags, const char *line, unsigned length, struct uns uns) +{ + int qkept, glob; + unsigned count; + struct slice slice; + struct entry *entries; + struct value *value; + + /* is querying only @s? */ + qkept = length > 0 && line[0] == KEEP_CHAR; + if (qkept) { + line++; + length--; + } + + entries = ptags->entries; + count = ptags->count; + + /* check length */ + if (length == 0) { + /* + * global query + */ + glob = 1; + slice.lower = 0; + slice.upper = count; + } else { + /* is a global query? */ + glob = length > 1 && line[length - 2] == SEPAR_CHAR + && line[length - 1] == GLOB_CHAR; + if (glob) { + /* global */ + length -= 1; + if (!is_valid_prefix(line, length)) + return -EINVAL; + } else { + /* not global */ + if (!is_valid_tag(line, length)) + return -EINVAL; + } + /* search entry slice */ + slice = entries_search(entries, count, line, length, glob); + } + + /* iterate over found entries */ + while (slice.lower != slice.upper) { + value = entry_read(&entries[slice.lower++], uns); + if (value && (!qkept || value_is_kept(*value))) + return 0; + } + return -ENOENT; +} + +/** + * _ptags_set - set the value of one tag + * + * @cptags: controling ptags + * @mptags: modified ptags + * @line: string for the line of setting the tag + * @length: length in bytes of the tag's line + * @uns: user namespace proxy + * + * Returns 0 in case of success tag present or + * o -ENOENT if the tag is not found + * o -EINVAL if the syntax is invalid + * o -EPERM if the operation is forbidden + * o -ENOMEM if not enough memory + */ +static int _ptags_set(struct _ptags *cptags, struct _ptags *mptags, + const char *line, unsigned length, struct uns uns) +{ + struct slice slice; + unsigned taglen, idxval, vallen; + struct entry *entry; + struct item *item; + struct value *value; + int rc; + + /* compute the tag length */ + taglen = 0; + while (taglen < length && line[taglen] != ASSIGN_CHAR) + taglen++; + if (!is_valid_tag(line, taglen)) + return -EINVAL; + + /* search the permission */ + if (!check_tag + (cptags, mptags, line, taglen, set_string, set_string_length, uns)) + return -EPERM; + + /* search the entry */ + slice = entries_search(mptags->entries, mptags->count, line, taglen, 0); + if (slice.lower == slice.upper) + return -ENOENT; + + /* instanciate the item */ + idxval = taglen + 1; + if (length <= idxval) + item = NULL; + else { + /* check validity of item */ + vallen = length - idxval; + if (!is_valid_value(line + idxval, vallen)) + return -EINVAL; + /* create the item */ + item = item_create(line + idxval, vallen); + if (!item) + return -ENOMEM; + } + + /* create a value for writing */ + entry = &mptags->entries[slice.lower]; + rc = _ptags_entry_write(mptags, entry, uns, &value); + if (rc <= 0) { + item_unref_safe(item); + /* test the case of deleted entry */ + return rc ? -ENOMEM : -ENOENT; + } + + /* replace the previous value */ + value_set(value, item); + return 0; +} + +/** + * _ptags_sub - Removes one or more tags + * + * @cptags: controling ptags + * @mptags: modified ptags + * @line: string for the line of the tag + * @length: length in bytes of the tag's line + * @uns: user namespace proxy + * + * Returns 0 in case of success or + * o -EINVAL if the syntax is invalid + * o -EPERM if the operation is forbiden + */ +static int _ptags_sub(struct _ptags *cptags, struct _ptags *mptags, + const char *line, unsigned length, struct uns uns) +{ + struct slice slice; + int subkept, glob, rc; + unsigned i, j, count; + struct entry *entries, *entry, e; + struct value *value; + + /* is for removing @s? */ + subkept = length > 0 && line[0] == KEEP_CHAR; + if (subkept) { + line++; + length--; + } + + /* init */ + entries = mptags->entries; + count = mptags->count; + + /* check length */ + if (length == 0) { + /* + * global selection of all + */ + glob = 1; + slice.lower = 0; + slice.upper = count; + } else { + /* is a global sub? */ + glob = length > 1 && line[length - 2] == SEPAR_CHAR + && line[length - 1] == GLOB_CHAR; + if (glob) { + /* global */ + length -= 1; + if (!is_valid_prefix(line, length)) + return -EINVAL; + } else { + /* not global */ + if (!is_valid_tag(line, length)) + return -EINVAL; + } + /* search entry slice */ + slice = entries_search(entries, count, line, length, glob); + } + + /* check action */ + if (subkept) { + /* remove kept flags */ + for (i = slice.lower; i < slice.upper; i++) { + entry = &entries[i]; + if (check_entry (cptags, mptags, entry, sub_string, sub_string_length, uns)) { + /* authorized */ + rc = _ptags_entry_write(mptags, entry, uns, &value); + if (rc < 0) + return -ENOMEM; + if (rc) + value_clear_kept(value); + } else if (!glob) { + /* not authorized and not global */ + value = entry_read(entry, uns); + if (value && value_is_kept(*value)) + return -EPERM; + } + } + } else { + /* mark entries to remove */ + for (i = slice.lower; i < slice.upper; i++) { + entry = &entries[i]; + if (check_entry (cptags, mptags, entry, sub_string, sub_string_length, uns)) { + /* authorized */ + rc = _ptags_entry_write(mptags, entry, uns, &value); + if (rc < 0) + return -ENOMEM; + if (rc) + value_set_removed(value); + } else if (!glob) { + /* not authorized and not global */ + value = entry_read(entry, uns); + if (value) + return -EPERM; + } + } + /* remove entries */ + for (i = j = slice.lower; i < slice.upper; i++) { + e = entries[i]; + if (entry_is_removed(e)) + entry_erase(e); + else { + if (i != j) + entries[j] = e; + j++; + } + } + if (i != j) { + if (i != count) + memmove(&entries[j], &entries[i], (count - i) * sizeof *entry); + mptags->count -= i - j; + _ptags_query_gc(mptags); + } + } + return 0; +} + +/** + * _ptags_add - Adds one tag + * + * @cptags: controling ptags + * @mptags: modified ptags + * @line: string for the line of the tag + * @length: length in bytes of the tag's line + * @uns: user namespace proxy + * + * Returns 0 in case of success or one of the following error code if failed: + * o -EINVAL if the line is invalid + * o -EPERM if the addition is forbidden + * o -ENOMEM if the an allocation failed + * o -ECANCELED if the maximum count of tag is reached + */ +static int _ptags_add(struct _ptags *cptags, struct _ptags *mptags, + const char *line, unsigned length, struct uns uns) +{ + struct slice slice; + int addkept, glob, rc; + unsigned i, n, count; + struct entry *entries, *entry; + struct item *name; + struct value *value; + + /* is for adding @s? */ + addkept = length > 0 && line[0] == KEEP_CHAR; + if (addkept) { + line++; + length--; + } + + entries = mptags->entries; + count = mptags->count; + + /* check length */ + if (length == 0) { + /* + * global selection of all + */ + glob = 1; + slice.lower = 0; + slice.upper = count; + } else { + /* is a global sub? */ + glob = length > 1 && line[length - 2] == SEPAR_CHAR + && line[length - 1] == GLOB_CHAR; + if (glob) { + length -= 1; + if (!is_valid_prefix(line, length)) + return -EINVAL; + } else { + /* not global */ + if (!is_valid_tag(line, length)) + return -EINVAL; + } + } + if (glob && !addkept) + return -EINVAL; + + /* search entry slice */ + slice = entries_search(entries, count, line, length, glob); + + /* check action */ + if (glob) { + /* globally add kept flags to existing */ + for (i = slice.lower; i < slice.upper; i++) { + entry = &entries[i]; + if (check_entry (cptags, mptags, entry, add_string, add_string_length, uns)) { + /* authorized */ + rc = _ptags_entry_write(mptags, entry, uns, &value); + if (rc < 0) + return -ENOMEM; + if (rc) + value_set_kept(value); + } + } + } else if (slice.lower != slice.upper) { + /* add kept if needed */ + entry = &entries[slice.lower]; + if (check_entry (cptags, mptags, entry, add_string, add_string_length, uns)) { + /* authorized */ + value = _ptags_entry_create(mptags, entry, uns); + if (!value) + return -ENOMEM; + if (addkept) + value_set_kept(value); + } else { + /* not authorized */ + value = entry_read(entry, uns); + if (!value || (addkept && !value_is_kept(*value))) + return -EPERM; + } + } else { + /* adds a new entry */ + if (count == MAXCOUNT) + return -ECANCELED; + if (!check_tag (cptags, mptags, line, length, add_string, add_string_length, uns)) + return -EPERM; + if (count == mptags->capacity) { + n = mptags->capacity + CAPACITYINCR; + entries = krealloc(entries, n * sizeof *entries, GFP_KERNEL); + if (!entries) + return -ENOMEM; + mptags->entries = entries; + mptags->capacity = n; + } + name = item_create(line, length); + if (!name) + return -ENOMEM; + entry = &entries[slice.lower]; + mptags->count = count + 1; + n = count - slice.lower; + if (n) + memmove(&entry[1], entry, n * sizeof *entry); + *entry = entry_make(name); + value = _ptags_entry_create(mptags, entry, uns); + if (!value) + return -ENOMEM; + if (addkept) + value_set_kept(value); + } + return 0; +} + +/** + * _ptags_write - Implement the writing of the tags + * + * @cptags: controling ptags + * @mptags: modified ptags + * @buffer: a pointer to the written data + * @size: the size of the written data + * @uns: user namespace proxy + * + * Returns the positive count of byte written. It can be less than the + * count given by size if an error appears after. This count indicates + * the count of data treated without errors. + * Returns one of the negative error code below if the data begins on error: + * o -EINVAL if the name or the syntax is invalid + * o -EPERM if the addition is forbidden + * o -ENOMEM if the an allocation failed + * o -ENOENT if the query failed + * o -ECANCELED if the maximum count of tag is reached + */ +static int _ptags_write(struct _ptags *cptags, struct _ptags *mptags, + const char *buffer, unsigned size, struct uns uns) +{ + unsigned start, stop, len; + int err; + + /* begin the parsing */ + start = 0; + while (start < size) { + /* scan a line of 'len' */ + for (stop = start; stop < size && buffer[stop] != EOL_CHAR; + stop++) ; + len = stop - start; + + /* ignore empty lines */ + if (len == 0) + err = 0; + + /* check utf8 validity of the line */ + else if (!is_valid_utf8(buffer + start, len)) + err = -EINVAL; + + /* lines not terminated with EOL_CHAR */ + else if (stop == size) + err = -EINVAL; + + /* skip comments (starting with COMMENT_CHAR) */ + else if (buffer[start] == COMMENT_CHAR) + err = 0; + + /* line starting with ADD_CHAR */ + else if (buffer[start] == ADD_CHAR) + err = _ptags_add(cptags, mptags, + buffer + start + 1, len - 1, uns); + + /* lines starting with SUB_CHAR */ + else if (buffer[start] == SUB_CHAR) + err = _ptags_sub(cptags, mptags, + buffer + start + 1, len - 1, uns); + + /* lines starting with SET_CHAR */ + else if (buffer[start] == SET_CHAR) + err = _ptags_set(cptags, mptags, + buffer + start + 1, len - 1, uns); + + /* lines starting with QUERY_CHAR */ + else if (buffer[start] == QUERY_CHAR) + err = _ptags_query(mptags, buffer + start + 1, len - 1, uns); + + /* other lines */ + else + err = -EINVAL; + + /* treat the error case if any */ + if (err != 0) { + return start ? (int)start : err; + } + + /* parse next line */ + start = stop + 1; + } + return (int)start; +} + +/** + * _ptags_read - Implement the reading of the tags + * + * @ptags: tags structure of the readen task + * @result: a pointer for storing the read result + * @uns: user namespace proxy + * + * Returns the count of byte read or the negative code -ENOMEM + * if an allocation failed. + */ +static int _ptags_read(struct _ptags *ptags, char **result, struct uns uns) +{ + unsigned idx, count; + size_t size; + struct entry *entries, *entry; + char *buffer; + struct value *value; + struct item *item; + + /* init loops */ + count = ptags->count; + entries = ptags->entries; + + /* compute printed size */ + size = 0; + for (idx = 0; idx < count; idx++) { + entry = &entries[idx]; + value = entry_read(entry, uns); + if (value) { + item = value_get(*value); + size += entry_name(*entry)->length + + (unsigned)value_is_kept(*value) + + (item ? 2 + item->length : 1); + } + } + + if (size > INT_MAX) + return -E2BIG; + buffer = kmalloc(size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + /* print in the buffer */ + *result = buffer; + for (idx = 0; idx < count; idx++) { + entry = &entries[idx]; + value = entry_read(entry, uns); + if (value) { + if (value_is_kept(*value)) + *buffer++ = KEEP_CHAR; + item = entry_name(*entry); + memcpy(buffer, item->value, item->length); + buffer += item->length; + item = value_get(*value); + if (item) { + *buffer++ = ASSIGN_CHAR; + memcpy(buffer, item->value, item->length); + buffer += item->length; + } + *buffer++ = EOL_CHAR; + } + } + + return (int)size; +} + +/******************************************************************* + * section: ptags + ******************************************************************/ + +/* + * ptags_lock - Locks one ptags + * + * @ptags: the ptags to lock + */ +static inline void ptags_lock(struct ptags *ptags) +{ + mutex_lock(&ptags->lock); +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + _ptags_clean_nsrefs(&ptags->data); +#endif +} + +/* + * ptags_unlock - Unlocks one ptags + * + * @ptags: the ptags to unlock + */ +static inline void ptags_unlock(struct ptags *ptags) +{ +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + if (ptags->data.wantgc) + _ptags_collect_garbage(&ptags->data); +#endif + mutex_unlock(&ptags->lock); +} + +/* + * ptags_lock2 - Locks 2 ptags avoiding deadlocks + * + * @ptags1: one of the ptags to lock + * @ptags2: the other of the ptags to lock + */ +static inline void ptags_lock2(struct ptags *ptags1, struct ptags *ptags2) +{ + if ((uintptr_t) ptags1 < (uintptr_t) ptags2) { + mutex_lock(&ptags1->lock); + mutex_lock(&ptags2->lock); + } else { + mutex_lock(&ptags2->lock); + mutex_lock(&ptags1->lock); + } +#ifdef CONFIG_SECURITY_PTAGS_WITH_USER_NS + _ptags_clean_nsrefs(&ptags1->data); + _ptags_clean_nsrefs(&ptags2->data); +#endif +} + +/* + * ptags_unlock2 - Unlocks 2 ptags locked together + * + * @ptags1: one of the ptags to unlock + * @ptags2: the other of the ptags to unlock + */ +static inline void ptags_unlock2(struct ptags *ptags1, struct ptags *ptags2) +{ + ptags_unlock(ptags1); + ptags_unlock(ptags2); +} + +/** + * ptags_write - Implement the writing of the tags + * + * @cptags: controling ptags + * @mptags: modified ptags + * @buffer: a pointer to the written data + * @size: the size of the written data + * + * Returns the positive count of byte written. It can be less than the + * count given by size if an error appears after. This count indicates + * the count of data treated without errors. + * Returns one of the negative error code below if the data begins on error: + * o -EINVAL if the name or the syntax is invalid + * o -EPERM if the addition is forbidden + * o -ENOMEM if the an allocation failed + * o -ENOENT if the query failed + * o -ECANCELED if the maximum count of tag is reached + */ +static int ptags_write(struct ptags *cptags, struct ptags *mptags, + const char *buffer, size_t size) +{ + int result; + unsigned length; + struct uns uns; + + /* crop the length */ + length = (size > (size_t) INT_MAX) ? INT_MAX : (unsigned)size; + + /* lock the ptags */ + uns = uns_get(); + if (!cptags || cptags == mptags) { + ptags_lock(mptags); + result = _ptags_write(cptags ? &cptags->data : NULL, &mptags->data, buffer, length, uns); + ptags_unlock(mptags); + } else { + ptags_lock2(cptags, mptags); + result = _ptags_write(&cptags->data, &mptags->data, buffer, length, uns); + ptags_unlock2(cptags, mptags); + } + uns_put(uns); + + return result; +} + +/** + * ptags_read - Implement the reading of the tags + * + * @ptags: tags structure of the readen task + * @data: a pointer for storing the read data + * + * Returns the count of byte read or the negative code -ENOMEM + * if an allocation failed. + */ +static int ptags_read(struct ptags *ptags, char **data) +{ + int result; + struct uns uns; + + uns = uns_get(); + ptags_lock(ptags); + result = _ptags_read(&ptags->data, data, uns); + ptags_unlock(ptags); + uns_put(uns); + + return result; +} + +/* + * ptags_free - Frees ptags + * + * @ptags: the ptags to free + */ +static void ptags_free(struct ptags *ptags) +{ + if (ptags) { + _ptags_erase(&ptags->data); + kfree(ptags); + } +} + +/** + * ptags_copy - Copies entries from 'src' to 'dst' + * + * @dst: destination ptags + * @src: source ptags + * + * Returns 0 on success or -ENOMEM on memory allocation failure. + */ +static int ptags_copy(struct ptags *dst, struct ptags *src) +{ + int rc; + + ptags_lock2(dst, src); + rc = _ptags_copy(&dst->data, &src->data); + ptags_unlock2(dst, src); + return rc; +} + +/** + * ptags_move - Transfers entries from 'src' to 'dst' + * + * @dst: destination ptags + * @src: source ptags + */ +static void ptags_move(struct ptags *dst, struct ptags *src) +{ + ptags_lock2(dst, src); + _ptags_move(&dst->data, &src->data); + ptags_unlock2(dst, src); +} + +/** + * ptags_prune - Prunes from 'ptags' the entries not kept + * + * @ptags: the ptags to be puned + */ +static void ptags_prune(struct ptags *ptags) +{ + ptags_lock(ptags); + _ptags_prune(&ptags->data); + ptags_unlock(ptags); +} + +/* + * ptags_create - Creates and initializes the ptags structure + * + * Returns the created ptags. + */ +static struct ptags *ptags_create(void) +{ + struct ptags *ptags; + + ptags = kmalloc(sizeof *ptags, GFP_KERNEL); + if (ptags) { + mutex_init(&ptags->lock); + _ptags_init(&ptags->data); + } + return ptags; +} + + diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 09fd610..fe486d3 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -5784,6 +5784,10 @@ static int selinux_getprocattr(struct task_struct *p, int error; unsigned len; +#ifdef CONFIG_SECURITY_PTAGS + if (strcmp(name, "ptags") == 0) + return 0; +#endif if (current != p) { error = current_has_perm(p, PROCESS__GETATTR); if (error) @@ -5831,6 +5835,10 @@ static int selinux_setprocattr(struct task_struct *p, int error; char *str = value; +#ifdef CONFIG_SECURITY_PTAGS + if (strcmp(name, "ptags") == 0) + return 0; +#endif if (current != p) { /* SELinux only allows a process to change its own security attributes. */ diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 1cb0602..28678e9 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -3603,13 +3603,18 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) */ static int smack_getprocattr(struct task_struct *p, char *name, char **value) { - struct smack_known *skp = smk_of_task_struct(p); + struct smack_known *skp; char *cp; int slen; +#ifdef CONFIG_SECURITY_PTAGS + if (strcmp(name, "ptags") == 0) + return 0; +#endif if (strcmp(name, "current") != 0) return -EINVAL; + skp = smk_of_task_struct(p); cp = kstrdup(skp->smk_known, GFP_KERNEL); if (cp == NULL) return -ENOMEM; @@ -3634,12 +3639,16 @@ static int smack_getprocattr(struct task_struct *p, char *name, char **value) static int smack_setprocattr(struct task_struct *p, char *name, void *value, size_t size) { - struct task_smack *tsp = current_security(); + struct task_smack *tsp; struct cred *new; struct smack_known *skp; struct smack_known_list_elem *sklep; int rc; +#ifdef CONFIG_SECURITY_PTAGS + if (strcmp(name, "ptags") == 0) + return 0; +#endif /* * Changing another process' Smack value is too dangerous * and supports no sane use case. @@ -3647,6 +3656,7 @@ static int smack_setprocattr(struct task_struct *p, char *name, if (p != current) return -EPERM; + tsp = current_security(); if (!smack_privileged(CAP_MAC_ADMIN) && list_empty(&tsp->smk_relabel)) return -EPERM; -- 2.7.4