From patchwork Thu Sep 29 10:40:49 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Jos=C3=A9_Bollo?= X-Patchwork-Id: 9356259 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 3A44F6077B for ; Thu, 29 Sep 2016 10:47:37 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 21DC629946 for ; Thu, 29 Sep 2016 10:47:37 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 1604429949; Thu, 29 Sep 2016 10:47:37 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.8 required=2.0 tests=BAYES_00,DKIM_SIGNED, RCVD_IN_DNSWL_HI,T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id CA16A29946 for ; Thu, 29 Sep 2016 10:47:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933796AbcI2Kr2 (ORCPT ); Thu, 29 Sep 2016 06:47:28 -0400 Received: from erza.lautre.net ([80.67.160.89]:56429 "EHLO erza.lautre.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1755893AbcI2Kqz (ORCPT ); Thu, 29 Sep 2016 06:46:55 -0400 Received: from localhost (localhost [127.0.0.1]) by erza.lautre.net (Postfix) with ESMTP id 753D9E7C7E; Thu, 29 Sep 2016 12:41:15 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=nonadev.net; s=alternc; t=1475145678; bh=FmIudfGb8siRMBXu5EkdCqp3k+BF5EGKu0LviGklBkE=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=xZ6WmuoP4QXVlkU71z/mlkLLxB5UJ4a4HZVHQflT/vkztZBovTkRil+5CJhiDzHGH 5q9j8aT/W3TnuYSer7ks7BpL3FKhs+GrOJaxhXBioFEVNwPcpBpALd9rX8Zoutynv0 FElQCxgKs8gV/bEgM6OHTOPTZKaaqqfsVR55QBOQ= X-Virus-Scanned: Debian amavisd-new at erza.lautre.net Received: from erza.lautre.net ([127.0.0.1]) by localhost (erza.admin.lautre.net [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id eQ4e5EBoweVt; Thu, 29 Sep 2016 12:41:09 +0200 (CEST) Received: from d-jobol.iot.bzh (por56-1-78-229-206-229.fbx.proxad.net [78.229.206.229]) by erza.lautre.net (Postfix) with ESMTPA id 0DD7FE7C64; Thu, 29 Sep 2016 12:41:07 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=nonadev.net; s=alternc; t=1475145668; bh=FmIudfGb8siRMBXu5EkdCqp3k+BF5EGKu0LviGklBkE=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=gq4Q6FvyavDjiSYAkbFy7FIfX4uMvlP3H7zCIisZgAVH8iqNu03g0zDj0RluZuLiV rfOgilP9Oe2pKE/V7G7hwwWYrY51qynHRWi03yjojykoPxvlhu9DHjV8vlPSu6Pru9 DBwvjgQfTGGuZSryUePCBWlWYzu2haCdVK7qmzy4= From: jobol@nonadev.net To: linux-security-module@vger.kernel.org Cc: =?UTF-8?q?Jos=C3=A9=20Bollo?= Subject: [RFC PATCH v1 1/1] LSM ptags: Add tagging of processes Date: Thu, 29 Sep 2016 12:40:49 +0200 Message-Id: <1475145649-24293-2-git-send-email-jobol@nonadev.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1475145649-24293-1-git-send-email-jobol@nonadev.net> References: <1475145649-24293-1-git-send-email-jobol@nonadev.net> MIME-Version: 1.0 Sender: owner-linux-security-module@vger.kernel.org Precedence: bulk List-ID: X-Virus-Scanned: ClamAV using ClamSMTP 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 + security/Kconfig | 1 + security/Makefile | 2 + security/apparmor/lsm.c | 14 +- security/ptags/Kconfig | 8 + security/ptags/Makefile | 6 + security/ptags/lsm-ptags.c | 204 ++++++ security/ptags/ptags.c | 1448 ++++++++++++++++++++++++++++++++++++++ security/selinux/hooks.c | 8 + security/smack/smack_lsm.c | 14 +- 12 files changed, 2135 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/lsm-ptags.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 ac0df4d..d2dde95 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -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) diff --git a/include/linux/cred.h b/include/linux/cred.h index 257db64..fbb29eb 100644 --- a/include/linux/cred.h +++ b/include/linux/cred.h @@ -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. */ 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..c548562 --- /dev/null +++ b/security/ptags/Kconfig @@ -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//attr/ptags + diff --git a/security/ptags/Makefile b/security/ptags/Makefile new file mode 100644 index 0000000..da26539 --- /dev/null +++ b/security/ptags/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for the Tag module +# + +obj-$(CONFIG_SECURITY_PTAGS) := lsm-ptags.o + diff --git a/security/ptags/lsm-ptags.c b/security/ptags/lsm-ptags.c new file mode 100644 index 0000000..87fe6d7 --- /dev/null +++ b/security/ptags/lsm-ptags.c @@ -0,0 +1,204 @@ +/* + * 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 + +#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); diff --git a/security/ptags/ptags.c b/security/ptags/ptags.c new file mode 100644 index 0000000..dfd4f6b --- /dev/null +++ b/security/ptags/ptags.c @@ -0,0 +1,1448 @@ +/* + * 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 + */ + +/* + * 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; +} diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 13185a6..9235d4e 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -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. */ diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 87a9741..2fd7f26 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -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;