new file mode 100644
@@ -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/<PID>/attr/ptags and /proc/<PID>/tasks/<TID>/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/<PID>/attr/ptags or to /proc/<PID>/tasks/<TID>/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/<PID>/attr/ptags as if the following
+command was issued:
+
+ $ echo "+S:PERMISSION-NAME" > /proc/<PID>/attr/ptags
+
+This will add the tags S:PERMISSION-NAME to the process of <PID>.
+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/<PID>/attr/ptags as if the following
+command was issued:
+
+ $ grep -q '^S:PERMISSION-NAME$' /proc/<PID>/attr/ptags
+
+The following command use ptags protocol and has the same behaviour:
+
+ $ echo "?S:PERMISSION-NAME" > /proc/<PID>/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/<PID>/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/<PID>/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
+
@@ -2497,6 +2497,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)
@@ -148,6 +148,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. */
@@ -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
@@ -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
@@ -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 */
new file mode 100644
@@ -0,0 +1,8 @@
+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/<pid>/attr/ptags
+
new file mode 100644
@@ -0,0 +1,6 @@
+#
+# Makefile for the Tag module
+#
+
+obj-$(CONFIG_SECURITY_PTAGS) := lsm-ptags.o
+
new file mode 100644
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 José Bollo <jobol@nonadev.net>
+ *
+ * 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 <jobol@nonadev.net>
+ */
+
+#include <linux/types.h>
+
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/sched.h>
+#include <linux/binfmts.h>
+#include <linux/lsm_hooks.h>
+#include <linux/printk.h>
+
+#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)
+ || ns_capable(task_cred_xxx(task, user_ns), CAP_MAC_ADMIN))
+ 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);
new file mode 100644
@@ -0,0 +1,1448 @@
+/*
+ * Copyright (C) 2016 José Bollo <jobol@nonadev.net>
+ *
+ * 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 <jobol@nonadev.net>
+ */
+
+/*
+ * Definition of characters at the beginning of lines
+ */
+#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) */
+
+/*
+ * 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))
+
+/*
+ * ptags slice
+ */
+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 {
+ unsigned refcount; /* reference count of the string */
+ unsigned length; /* length of the string (without terminal zero) */
+ char value[1]; /* the zero terminated string */
+};
+
+/*
+ * entries record ptags, their value and kept flag
+ */
+struct entry {
+ struct item *name; /* the item for the name */
+ uintptr_t data; /* item for the value with used lower bits */
+ /* bit 0: kept flag */
+ /* bit 1: removed flag */
+};
+
+/*
+ * ptags data attached to tasks
+ */
+struct ptags {
+ struct mutex lock; /* mutex access */
+ struct entry *entries; /* array of entries */
+ unsigned count; /* count of entries */
+ unsigned capacity; /* allocated count of entries in entries */
+};
+
+/*
+ * mutex for accessing item's
+ */
+static DEFINE_MUTEX(itemlock);
+
+/*******************************************************************
+ * 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 a string of data that is used either
+ * for storing the tags or their values.
+ * This structure is reference counted.
+ ******************************************************************/
+
+/*
+ * item_create - Creates an item for the 'data' of 'length'
+ *
+ * @data: the data copied as value of the item
+ * @length: length in bytes of the data
+ *
+ * Returns the create item or NULL on error.
+ */
+static struct item *item_create(const char *data, unsigned length)
+{
+ struct item *item;
+
+ item = kmalloc(length + sizeof *item, GFP_KERNEL);
+ if (item) {
+ item->refcount = 1;
+ item->length = length;
+ memcpy(item->value, data, 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 or NULL on error
+ */
+static struct item *item_addref(struct item *item)
+{
+ unsigned n;
+ mutex_lock(&itemlock);
+ n = item->refcount + 1;
+ if (n != 0)
+ item->refcount = n;
+ else
+ item = NULL;
+ mutex_unlock(&itemlock);
+ return item;
+}
+
+/*
+ * item_addref - Adds a reference to 'item' and returns it
+ *
+ * @item: the item to use (can be NULL)
+ *
+ * Returns the item or NULL on error
+ */
+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 void item_unref(struct item *item)
+{
+ unsigned n;
+
+ mutex_lock(&itemlock);
+ n = item->refcount - 1;
+ item->refcount = n;
+ mutex_unlock(&itemlock);
+ if (n <= 0)
+ 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: 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;
+}
+
+/*
+ * entry_value - Gets the value of 'entry'
+ *
+ * @entry: the entry whose value is to get
+ *
+ * Returns the value that can be NULL of 'entry'
+ */
+static inline struct item *entry_value(struct entry entry)
+{
+ return (struct item *)(entry.data & ~(uintptr_t) 3);
+}
+
+/*
+ * entry_set_value - Sets the value of 'entry' to 'value'
+ *
+ * @entry: the entry whose value is to set
+ * @value: the value to set (can be NULL)
+ */
+static inline void entry_set_value(struct entry *entry, struct item *value)
+{
+ item_unref_safe(entry_value(*entry));
+ entry->data &= (uintptr_t) 3;
+ entry->data |= (uintptr_t) value;
+}
+
+/*
+ * entry_is_removed - Is the 'entry' removed?
+ *
+ * @entry: the entry to test
+ *
+ * Returns 1 if entry is removed or 0 else
+ */
+static inline int entry_is_removed(struct entry entry)
+{
+ return (int)(entry.data & (uintptr_t) 2);
+}
+
+/*
+ * entry_set_removed - Sets the 'entry' as removed
+ *
+ * @entry: the entry to set
+ */
+static inline void entry_set_removed(struct entry *entry)
+{
+ entry->data |= (uintptr_t) 2;
+}
+
+/*
+ * entry_is_kept - Is the 'entry' to be kept?
+ *
+ * @entry: the entry to test
+ *
+ * Returns 1 if entry is kept or 0 else
+ */
+static inline int entry_is_kept(struct entry entry)
+{
+ return (int)(entry.data & (uintptr_t) 1);
+}
+
+/*
+ * entry_set_kept - Sets the kept flag of the 'entry'
+ *
+ * @entry: the entry to set
+ */
+static inline void entry_set_kept(struct entry *entry)
+{
+ entry->data |= (uintptr_t) 1;
+}
+
+/*
+ * entry_clear_kept - Clears the kept flag of the 'entry'
+ *
+ * @entry: the entry to clear
+ */
+static inline void entry_clear_kept(struct entry *entry)
+{
+ entry->data &= ~(uintptr_t) 1;
+}
+
+/*
+ * entry_make - Makes a new entry
+ *
+ * @name: name of the entry
+ * @kept: should be kept?
+ * @value: value of the entry
+ *
+ * Returns the entry ionitialized with the given values
+ */
+static inline struct entry entry_make(struct item *name, int kept,
+ struct item *value)
+{
+ struct entry result;
+ result.name = name;
+ result.data = (uintptr_t) (kept != 0) | (uintptr_t) value;
+ return result;
+}
+
+/*
+ * entry_clone - Clones the 'entry'
+ *
+ * @entry: the entry to clone
+ *
+ * Returns an entry with same data than 'entry' but whose reference
+ * counts are incremented.
+ */
+static inline struct entry entry_clone(struct entry entry)
+{
+ return entry_make(item_addref(entry_name(entry)), entry_is_kept(entry),
+ item_addref_safe(entry_value(entry)));
+}
+
+/*
+ * 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)
+{
+ item_unref_safe(entry_value(entry));
+ item_unref(entry_name(entry));
+}
+
+/*
+ * entry_print_length - Computes the count of bytes needed to print 'entry'
+ *
+ * @entry: the entry to print
+ */
+static unsigned entry_print_length(struct entry entry)
+{
+ struct item *value = entry_value(entry);
+ return (unsigned)entry_is_kept(entry)
+ + entry_name(entry)->length + (value ? 2 + value->length : 1);
+}
+
+/*
+ * entry_print - Prints the 'entry' in 'head'
+ *
+ * @entry: the entry to print
+ * @head: the head of the buffer where to print the entry
+ *
+ * Returns the buffer offsetted by the printed length
+ */
+static char *entry_print(struct entry entry, char *head)
+{
+ struct item *item;
+
+ if (entry_is_kept(entry))
+ *head++ = KEEP_CHAR;
+
+ item = entry_name(entry);
+ memcpy(head, item->value, item->length);
+ head += item->length;
+
+ item = entry_value(entry);
+ if (item) {
+ *head++ = ASSIGN_CHAR;
+ memcpy(head, item->value, item->length);
+ head += item->length;
+ }
+ *head++ = '\n';
+ return head;
+}
+
+/*******************************************************************
+ * 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;
+
+ 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:
+ 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_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);
+ }
+}
+
+/*
+ * ptags_lock2 - 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)
+{
+ mutex_unlock(&ptags1->lock);
+ mutex_unlock(&ptags2->lock);
+}
+
+/*
+ * 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;
+
+ count = ptags->count;
+ entries = ptags->entries;
+ ptags->entries = NULL;
+ ptags->count = 0;
+ ptags->capacity = 0;
+ while (count)
+ entry_erase(entries[--count]);
+ kfree(entries);
+}
+
+/*
+ * ptags_erase_safe - Erases the content of 'ptags' if not NULL
+ *
+ * @ptags: the ptags whose content is to erase (can be NULL)
+ */
+static inline void ptags_erase_safe(struct ptags *ptags)
+{
+ if (ptags)
+ ptags_erase(ptags);
+}
+
+/**
+ * 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, e;
+
+ mutex_lock(&ptags->lock);
+
+ entries = ptags->entries;
+ count = ptags->count;
+ for (i = j = 0; i < count; i++) {
+ e = entries[i];
+ if (!entry_is_kept(e))
+ entry_erase(e);
+ else
+ entries[j++] = e;
+ }
+ ptags->count = j;
+ mutex_unlock(&ptags->lock);
+}
+
+/**
+ * ptags_copy_locked - 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_locked(struct ptags *dst, struct ptags *src)
+{
+ unsigned i, count;
+ struct entry *to, *from;
+
+ /* creates the copy */
+ from = src->entries;
+ count = src->count;
+ to = kmalloc(count * sizeof *to, GFP_KERNEL);
+ if (!to)
+ return -ENOMEM;
+ for (i = 0; i < count; i++)
+ to[i] = entry_clone(from[i]);
+
+ /* assign the copy */
+ ptags_erase(dst);
+ dst->entries = to;
+ dst->count = count;
+ dst->capacity = count;
+
+ return 0;
+}
+
+/**
+ * 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_locked(dst, src);
+ 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_erase(dst);
+ dst->entries = src->entries;
+ dst->count = src->count;
+ dst->capacity = src->capacity;
+ src->entries = NULL;
+ src->count = 0;
+ src->capacity = 0;
+ ptags_unlock2(dst, src);
+}
+
+/*
+ * 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->entries = NULL;
+ ptags->count = 0;
+ ptags->capacity = 0;
+ }
+ return ptags;
+}
+
+/*
+ * ptags_free - Frees ptags
+ *
+ * @ptags: the ptags to free
+ */
+static void ptags_free(struct ptags *ptags)
+{
+ ptags_erase_safe(ptags);
+ kfree(ptags);
+}
+
+/*******************************************************************
+ * 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
+ *
+ * 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)
+{
+ 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++) {
+
+ /* 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
+ *
+ * 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)
+{
+ /* current ptags == NULL means "super capable" */
+ if (!cptags)
+ return 1;
+
+ /* check if the action is forbidden */
+ if (!check_action(cptags, tag, length, action, alen))
+ 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);
+}
+
+/*
+ * 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
+ *
+ * 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 item *name = entry_name(*entry);
+ return check_tag(cptags, mptags, name->value, name->length, action,
+ alen);
+}
+
+/*******************************************************************
+ * 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
+ *
+ * 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)
+{
+ int qkept, glob;
+ unsigned count;
+ struct slice slice;
+ struct entry *entries;
+
+ /* 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)
+ if (!qkept || entry_is_kept(entries[slice.lower++]))
+ 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
+ *
+ * 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 slice slice;
+ unsigned taglen, idxval, vallen;
+ struct item *value;
+
+ /* 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))
+ return -EPERM;
+
+ /* search the entry */
+ slice = entries_search(mptags->entries, mptags->count, line, taglen, 0);
+ if (slice.lower == slice.upper)
+ return -ENOENT;
+
+ /* instanciate the value */
+ idxval = taglen + 1;
+ if (length <= idxval)
+ value = NULL;
+ else {
+ /* check validity of value */
+ vallen = length - idxval;
+ if (!is_valid_value(line + idxval, vallen))
+ return -EINVAL;
+ /* create the value */
+ value = item_create(line + idxval, vallen);
+ if (!value)
+ return -ENOMEM;
+ }
+
+ /* replace the previous value */
+ entry_set_value(&mptags->entries[slice.lower], value);
+ 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
+ *
+ * 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 slice slice;
+ int subkept, glob;
+ unsigned i, j, count;
+ struct entry *entries, *entry, e;
+
+ /* 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))
+ entry_clear_kept(entry);
+ else if (!glob)
+ 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))
+ entry_set_removed(entry);
+ else if (!glob)
+ 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;
+ }
+ }
+ 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
+ *
+ * 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 slice slice;
+ int addkept, glob;
+ unsigned i, n, count;
+ struct entry *entries, *entry;
+ struct item *name;
+
+ /* is for adding @s? */
+ addkept = length > 0 && line[0] == KEEP_CHAR;
+ if (addkept) {
+ line++;
+ length--;
+ }
+
+ /* is a global sub? */
+ glob = length > 1 && line[length - 2] == SEPAR_CHAR
+ && line[length - 1] == GLOB_CHAR;
+ if (glob) {
+ /* global */
+ if (!addkept)
+ return -EINVAL;
+ length -= 1;
+ if (!is_valid_prefix(line, length))
+ return -EINVAL;
+ } else {
+ /* not global */
+ if (!is_valid_tag(line, length))
+ return -EINVAL;
+ }
+
+ /* search entry slice */
+ entries = mptags->entries;
+ count = mptags->count;
+ 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))
+ entry_set_kept(entry);
+ }
+ } else if (slice.lower != slice.upper) {
+ /* add kept if needed */
+ entry = &entries[slice.lower];
+ if (addkept && !entry_is_kept(*entry)) {
+ if (!check_entry
+ (cptags, mptags, entry, add_string,
+ add_string_length))
+ return -EPERM;
+ entry_set_kept(entry);
+ }
+ } else {
+ /* adds a new entry */
+ if (count == MAXCOUNT)
+ return -ECANCELED;
+ if (!check_tag
+ (cptags, mptags, line, length, add_string,
+ add_string_length))
+ 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, addkept, NULL);
+ }
+ return 0;
+}
+
+/**
+ * ptags_write_locked - 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_locked(struct ptags *cptags, struct ptags *mptags,
+ const char *buffer, unsigned size)
+{
+ 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] != '\n';
+ 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 '\n' */
+ 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);
+
+ /* lines starting with SUB_CHAR */
+ else if (buffer[start] == SUB_CHAR)
+ err = ptags_sub(cptags, mptags,
+ buffer + start + 1, len - 1);
+
+ /* lines starting with SET_CHAR */
+ else if (buffer[start] == SET_CHAR)
+ err = ptags_set(cptags, mptags,
+ buffer + start + 1, len - 1);
+
+ /* lines starting with QUERY_CHAR */
+ else if (buffer[start] == QUERY_CHAR)
+ err = ptags_query(mptags, buffer + start + 1, len - 1);
+
+ /* 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_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;
+
+ /* crop the length */
+ length = (size > (size_t) INT_MAX) ? INT_MAX : (unsigned)size;
+
+ /* lock the ptagss */
+ if (!cptags || cptags == mptags) {
+ mutex_lock(&mptags->lock);
+ result = ptags_write_locked(cptags, mptags, buffer, length);
+ mutex_unlock(&mptags->lock);
+ } else {
+ ptags_lock2(cptags, mptags);
+ result = ptags_write_locked(cptags, mptags, buffer, length);
+ ptags_unlock2(cptags, mptags);
+ }
+ return result;
+}
+
+/**
+ * ptags_read_locked - Implement the reading of the tags
+ *
+ * @ptags: tags structure of the readen task
+ * @result: a pointer for storing the read result
+ *
+ * Returns the count of byte read or the negative code -ENOMEM
+ * if an allocation failed.
+ */
+static int ptags_read_locked(struct ptags *ptags, char **result)
+{
+ unsigned idx, count;
+ size_t size;
+ struct entry *entries;
+ char *buffer;
+
+ count = ptags->count;
+ entries = ptags->entries;
+ size = 0;
+ for (idx = 0; idx < count; idx++)
+ size += entry_print_length(entries[idx]);
+
+ if (size > INT_MAX)
+ return -E2BIG;
+ buffer = kmalloc(size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ *result = buffer;
+ for (idx = 0; idx < count; idx++)
+ buffer = entry_print(entries[idx], buffer);
+
+ return (int)size;
+}
+
+/**
+ * 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;
+
+ mutex_lock(&ptags->lock);
+ result = ptags_read_locked(ptags, data);
+ mutex_unlock(&ptags->lock);
+ return result;
+}
@@ -5732,6 +5732,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)
@@ -5779,6 +5783,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. */
@@ -3603,13 +3603,18 @@ unlockandout:
*/
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;