2019-05-31 16:09:32 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-only
|
2005-04-17 06:20:36 +08:00
|
|
|
/* Updated: Karl MacMillan <kmacmillan@tresys.com>
|
|
|
|
*
|
2008-04-18 02:15:45 +08:00
|
|
|
* Added conditional policy language extensions
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
2011-08-01 19:10:33 +08:00
|
|
|
* Updated: Hewlett-Packard <paul@paul-moore.com>
|
2008-01-29 21:38:19 +08:00
|
|
|
*
|
2008-04-18 02:15:45 +08:00
|
|
|
* Added support for the policy capability bitmap
|
2008-01-29 21:38:19 +08:00
|
|
|
*
|
|
|
|
* Copyright (C) 2007 Hewlett-Packard Development Company, L.P.
|
2005-04-17 06:20:36 +08:00
|
|
|
* Copyright (C) 2003 - 2004 Tresys Technology, LLC
|
|
|
|
* Copyright (C) 2004 Red Hat, Inc., James Morris <jmorris@redhat.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/kernel.h>
|
|
|
|
#include <linux/pagemap.h>
|
|
|
|
#include <linux/slab.h>
|
|
|
|
#include <linux/vmalloc.h>
|
|
|
|
#include <linux/fs.h>
|
2019-03-26 00:38:30 +08:00
|
|
|
#include <linux/fs_context.h>
|
2018-03-20 23:59:11 +08:00
|
|
|
#include <linux/mount.h>
|
2006-03-22 16:09:14 +08:00
|
|
|
#include <linux/mutex.h>
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
#include <linux/namei.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/init.h>
|
|
|
|
#include <linux/string.h>
|
|
|
|
#include <linux/security.h>
|
|
|
|
#include <linux/major.h>
|
|
|
|
#include <linux/seq_file.h>
|
|
|
|
#include <linux/percpu.h>
|
2006-01-04 22:08:39 +08:00
|
|
|
#include <linux/audit.h>
|
2008-05-14 23:27:45 +08:00
|
|
|
#include <linux/uaccess.h>
|
2011-05-11 06:34:16 +08:00
|
|
|
#include <linux/kobject.h>
|
2011-05-27 02:59:25 +08:00
|
|
|
#include <linux/ctype.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/* selinuxfs pseudo filesystem for exporting the security policy API.
|
|
|
|
Based on the proc code and the fs/nfsd/nfsctl.c code. */
|
|
|
|
|
|
|
|
#include "flask.h"
|
|
|
|
#include "avc.h"
|
|
|
|
#include "avc_ss.h"
|
|
|
|
#include "security.h"
|
|
|
|
#include "objsec.h"
|
|
|
|
#include "conditional.h"
|
|
|
|
|
|
|
|
enum sel_inos {
|
|
|
|
SEL_ROOT_INO = 2,
|
|
|
|
SEL_LOAD, /* load policy */
|
|
|
|
SEL_ENFORCE, /* get or set enforcing status */
|
|
|
|
SEL_CONTEXT, /* validate context */
|
|
|
|
SEL_ACCESS, /* compute access decision */
|
|
|
|
SEL_CREATE, /* compute create labeling decision */
|
|
|
|
SEL_RELABEL, /* compute relabeling decision */
|
|
|
|
SEL_USER, /* compute reachable user contexts */
|
|
|
|
SEL_POLICYVERS, /* return policy version for this kernel */
|
|
|
|
SEL_COMMIT_BOOLS, /* commit new boolean values */
|
|
|
|
SEL_MLS, /* return if MLS policy is enabled */
|
|
|
|
SEL_DISABLE, /* disable SELinux until next reboot */
|
|
|
|
SEL_MEMBER, /* compute polyinstantiation membership decision */
|
|
|
|
SEL_CHECKREQPROT, /* check requested protection, not kernel-applied one */
|
[SECMARK]: Add new packet controls to SELinux
Add new per-packet access controls to SELinux, replacing the old
packet controls.
Packets are labeled with the iptables SECMARK and CONNSECMARK targets,
then security policy for the packets is enforced with these controls.
To allow for a smooth transition to the new controls, the old code is
still present, but not active by default. To restore previous
behavior, the old controls may be activated at runtime by writing a
'1' to /selinux/compat_net, and also via the kernel boot parameter
selinux_compat_net. Switching between the network control models
requires the security load_policy permission. The old controls will
probably eventually be removed and any continued use is discouraged.
With this patch, the new secmark controls for SElinux are disabled by
default, so existing behavior is entirely preserved, and the user is
not affected at all.
It also provides a config option to enable the secmark controls by
default (which can always be overridden at boot and runtime). It is
also noted in the kconfig help that the user will need updated
userspace if enabling secmark controls for SELinux and that they'll
probably need the SECMARK and CONNMARK targets, and conntrack protocol
helpers, although such decisions are beyond the scope of kernel
configuration.
Signed-off-by: James Morris <jmorris@namei.org>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2006-06-09 15:33:33 +08:00
|
|
|
SEL_COMPAT_NET, /* whether to use old compat network packet controls */
|
2007-09-22 02:37:10 +08:00
|
|
|
SEL_REJECT_UNKNOWN, /* export unknown reject handling to userspace */
|
|
|
|
SEL_DENY_UNKNOWN, /* export unknown deny handling to userspace */
|
selinux: fast status update interface (/selinux/status)
This patch provides a new /selinux/status entry which allows applications
read-only mmap(2).
This region reflects selinux_kernel_status structure in kernel space.
struct selinux_kernel_status
{
u32 length; /* length of this structure */
u32 sequence; /* sequence number of seqlock logic */
u32 enforcing; /* current setting of enforcing mode */
u32 policyload; /* times of policy reloaded */
u32 deny_unknown; /* current setting of deny_unknown */
};
When userspace object manager caches access control decisions provided
by SELinux, it needs to invalidate the cache on policy reload and setenforce
to keep consistency.
However, the applications need to check the kernel state for each accesses
on userspace avc, or launch a background worker process.
In heuristic, frequency of invalidation is much less than frequency of
making access control decision, so it is annoying to invoke a system call
to check we don't need to invalidate the userspace cache.
If we can use a background worker thread, it allows to receive invalidation
messages from the kernel. But it requires us an invasive coding toward the
base application in some cases; E.g, when we provide a feature performing
with SELinux as a plugin module, it is unwelcome manner to launch its own
worker thread from the module.
If we could map /selinux/status to process memory space, application can
know updates of selinux status; policy reload or setenforce.
A typical application checks selinux_kernel_status::sequence when it tries
to reference userspace avc. If it was changed from the last time when it
checked userspace avc, it means something was updated in the kernel space.
Then, the application can reset userspace avc or update current enforcing
mode, without any system call invocations.
This sequence number is updated according to the seqlock logic, so we need
to wait for a while if it is odd number.
Signed-off-by: KaiGai Kohei <kaigai@ak.jp.nec.com>
Acked-by: Eric Paris <eparis@redhat.com>
--
security/selinux/include/security.h | 21 ++++++
security/selinux/selinuxfs.c | 56 +++++++++++++++
security/selinux/ss/Makefile | 2 +-
security/selinux/ss/services.c | 3 +
security/selinux/ss/status.c | 129 +++++++++++++++++++++++++++++++++++
5 files changed, 210 insertions(+), 1 deletions(-)
Signed-off-by: James Morris <jmorris@namei.org>
2010-09-14 17:28:39 +08:00
|
|
|
SEL_STATUS, /* export current status using mmap() */
|
2010-10-14 05:50:25 +08:00
|
|
|
SEL_POLICY, /* allow userspace to read the in kernel policy */
|
2015-12-25 00:09:41 +08:00
|
|
|
SEL_VALIDATE_TRANS, /* compute validatetrans decision */
|
2007-04-05 04:18:39 +08:00
|
|
|
SEL_INO_NEXT, /* The next inode number to use */
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info {
|
|
|
|
struct dentry *bool_dir;
|
|
|
|
unsigned int bool_num;
|
|
|
|
char **bool_pending_names;
|
|
|
|
unsigned int *bool_pending_values;
|
|
|
|
struct dentry *class_dir;
|
|
|
|
unsigned long last_class_ino;
|
|
|
|
bool policy_opened;
|
|
|
|
struct dentry *policycap_dir;
|
|
|
|
unsigned long last_ino;
|
|
|
|
struct selinux_state *state;
|
|
|
|
struct super_block *sb;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int selinux_fs_info_create(struct super_block *sb)
|
|
|
|
{
|
|
|
|
struct selinux_fs_info *fsi;
|
|
|
|
|
|
|
|
fsi = kzalloc(sizeof(*fsi), GFP_KERNEL);
|
|
|
|
if (!fsi)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
fsi->last_ino = SEL_INO_NEXT - 1;
|
|
|
|
fsi->state = &selinux_state;
|
|
|
|
fsi->sb = sb;
|
|
|
|
sb->s_fs_info = fsi;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void selinux_fs_info_free(struct super_block *sb)
|
|
|
|
{
|
|
|
|
struct selinux_fs_info *fsi = sb->s_fs_info;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (fsi) {
|
|
|
|
for (i = 0; i < fsi->bool_num; i++)
|
|
|
|
kfree(fsi->bool_pending_names[i]);
|
|
|
|
kfree(fsi->bool_pending_names);
|
|
|
|
kfree(fsi->bool_pending_values);
|
|
|
|
}
|
|
|
|
kfree(sb->s_fs_info);
|
|
|
|
sb->s_fs_info = NULL;
|
|
|
|
}
|
2007-04-05 04:18:39 +08:00
|
|
|
|
2008-01-29 21:38:19 +08:00
|
|
|
#define SEL_INITCON_INO_OFFSET 0x01000000
|
|
|
|
#define SEL_BOOL_INO_OFFSET 0x02000000
|
|
|
|
#define SEL_CLASS_INO_OFFSET 0x04000000
|
|
|
|
#define SEL_POLICYCAP_INO_OFFSET 0x08000000
|
|
|
|
#define SEL_INO_MASK 0x00ffffff
|
2007-04-04 22:11:29 +08:00
|
|
|
|
2020-08-20 03:59:34 +08:00
|
|
|
#define BOOL_DIR_NAME "booleans"
|
|
|
|
#define CLASS_DIR_NAME "class"
|
|
|
|
#define POLICYCAP_DIR_NAME "policy_capabilities"
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#define TMPBUFLEN 12
|
|
|
|
static ssize_t sel_read_enforce(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;
|
2005-04-17 06:20:36 +08:00
|
|
|
char tmpbuf[TMPBUFLEN];
|
|
|
|
ssize_t length;
|
|
|
|
|
2018-03-02 07:48:02 +08:00
|
|
|
length = scnprintf(tmpbuf, TMPBUFLEN, "%d",
|
2018-03-20 23:59:11 +08:00
|
|
|
enforcing_enabled(fsi->state));
|
2005-04-17 06:20:36 +08:00
|
|
|
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
|
2005-04-17 06:20:36 +08:00
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *page = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
ssize_t length;
|
2018-03-02 07:48:02 +08:00
|
|
|
int old_value, new_value;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-10-31 06:59:24 +08:00
|
|
|
if (count >= PAGE_SIZE)
|
2015-12-24 13:08:06 +08:00
|
|
|
return -ENOMEM;
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
/* No partial writes. */
|
|
|
|
if (*ppos != 0)
|
2015-12-24 13:08:06 +08:00
|
|
|
return -EINVAL;
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2015-12-24 13:08:06 +08:00
|
|
|
page = memdup_user_nul(buf, count);
|
|
|
|
if (IS_ERR(page))
|
|
|
|
return PTR_ERR(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = -EINVAL;
|
|
|
|
if (sscanf(page, "%d", &new_value) != 1)
|
|
|
|
goto out;
|
|
|
|
|
2016-11-18 22:30:38 +08:00
|
|
|
new_value = !!new_value;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
old_value = enforcing_enabled(state);
|
2018-03-02 07:48:02 +08:00
|
|
|
if (new_value != old_value) {
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__SETENFORCE,
|
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2018-05-13 09:58:20 +08:00
|
|
|
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
|
2018-04-10 07:34:22 +08:00
|
|
|
"enforcing=%d old_enforcing=%d auid=%u ses=%u"
|
2019-12-17 22:15:10 +08:00
|
|
|
" enabled=1 old-enabled=1 lsm=selinux res=1",
|
2018-03-02 07:48:02 +08:00
|
|
|
new_value, old_value,
|
2012-08-20 15:09:36 +08:00
|
|
|
from_kuid(&init_user_ns, audit_get_loginuid(current)),
|
2019-12-17 22:15:10 +08:00
|
|
|
audit_get_sessionid(current));
|
2018-03-20 23:59:11 +08:00
|
|
|
enforcing_set(state, new_value);
|
2018-03-02 07:48:02 +08:00
|
|
|
if (new_value)
|
2018-03-06 00:47:56 +08:00
|
|
|
avc_ss_reset(state->avc, 0);
|
2018-03-02 07:48:02 +08:00
|
|
|
selnl_notify_setenforce(new_value);
|
2018-03-20 23:59:11 +08:00
|
|
|
selinux_status_update_setenforce(state, new_value);
|
2018-03-02 07:48:02 +08:00
|
|
|
if (!new_value)
|
2019-06-14 20:20:14 +08:00
|
|
|
call_blocking_lsm_notifier(LSM_POLICY_CHANGE, NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
length = count;
|
|
|
|
out:
|
2015-12-24 13:08:06 +08:00
|
|
|
kfree(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
return length;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define sel_write_enforce NULL
|
|
|
|
#endif
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_enforce_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.read = sel_read_enforce,
|
|
|
|
.write = sel_write_enforce,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2007-09-22 02:37:10 +08:00
|
|
|
static ssize_t sel_read_handle_unknown(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2007-09-22 02:37:10 +08:00
|
|
|
char tmpbuf[TMPBUFLEN];
|
|
|
|
ssize_t length;
|
2013-01-24 06:07:38 +08:00
|
|
|
ino_t ino = file_inode(filp)->i_ino;
|
2007-09-22 02:37:10 +08:00
|
|
|
int handle_unknown = (ino == SEL_REJECT_UNKNOWN) ?
|
2018-03-20 23:59:11 +08:00
|
|
|
security_get_reject_unknown(state) :
|
|
|
|
!security_get_allow_unknown(state);
|
2007-09-22 02:37:10 +08:00
|
|
|
|
|
|
|
length = scnprintf(tmpbuf, TMPBUFLEN, "%d", handle_unknown);
|
|
|
|
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations sel_handle_unknown_ops = {
|
|
|
|
.read = sel_read_handle_unknown,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2007-09-22 02:37:10 +08:00
|
|
|
};
|
|
|
|
|
selinux: fast status update interface (/selinux/status)
This patch provides a new /selinux/status entry which allows applications
read-only mmap(2).
This region reflects selinux_kernel_status structure in kernel space.
struct selinux_kernel_status
{
u32 length; /* length of this structure */
u32 sequence; /* sequence number of seqlock logic */
u32 enforcing; /* current setting of enforcing mode */
u32 policyload; /* times of policy reloaded */
u32 deny_unknown; /* current setting of deny_unknown */
};
When userspace object manager caches access control decisions provided
by SELinux, it needs to invalidate the cache on policy reload and setenforce
to keep consistency.
However, the applications need to check the kernel state for each accesses
on userspace avc, or launch a background worker process.
In heuristic, frequency of invalidation is much less than frequency of
making access control decision, so it is annoying to invoke a system call
to check we don't need to invalidate the userspace cache.
If we can use a background worker thread, it allows to receive invalidation
messages from the kernel. But it requires us an invasive coding toward the
base application in some cases; E.g, when we provide a feature performing
with SELinux as a plugin module, it is unwelcome manner to launch its own
worker thread from the module.
If we could map /selinux/status to process memory space, application can
know updates of selinux status; policy reload or setenforce.
A typical application checks selinux_kernel_status::sequence when it tries
to reference userspace avc. If it was changed from the last time when it
checked userspace avc, it means something was updated in the kernel space.
Then, the application can reset userspace avc or update current enforcing
mode, without any system call invocations.
This sequence number is updated according to the seqlock logic, so we need
to wait for a while if it is odd number.
Signed-off-by: KaiGai Kohei <kaigai@ak.jp.nec.com>
Acked-by: Eric Paris <eparis@redhat.com>
--
security/selinux/include/security.h | 21 ++++++
security/selinux/selinuxfs.c | 56 +++++++++++++++
security/selinux/ss/Makefile | 2 +-
security/selinux/ss/services.c | 3 +
security/selinux/ss/status.c | 129 +++++++++++++++++++++++++++++++++++
5 files changed, 210 insertions(+), 1 deletions(-)
Signed-off-by: James Morris <jmorris@namei.org>
2010-09-14 17:28:39 +08:00
|
|
|
static int sel_open_handle_status(struct inode *inode, struct file *filp)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;
|
|
|
|
struct page *status = selinux_kernel_status_page(fsi->state);
|
selinux: fast status update interface (/selinux/status)
This patch provides a new /selinux/status entry which allows applications
read-only mmap(2).
This region reflects selinux_kernel_status structure in kernel space.
struct selinux_kernel_status
{
u32 length; /* length of this structure */
u32 sequence; /* sequence number of seqlock logic */
u32 enforcing; /* current setting of enforcing mode */
u32 policyload; /* times of policy reloaded */
u32 deny_unknown; /* current setting of deny_unknown */
};
When userspace object manager caches access control decisions provided
by SELinux, it needs to invalidate the cache on policy reload and setenforce
to keep consistency.
However, the applications need to check the kernel state for each accesses
on userspace avc, or launch a background worker process.
In heuristic, frequency of invalidation is much less than frequency of
making access control decision, so it is annoying to invoke a system call
to check we don't need to invalidate the userspace cache.
If we can use a background worker thread, it allows to receive invalidation
messages from the kernel. But it requires us an invasive coding toward the
base application in some cases; E.g, when we provide a feature performing
with SELinux as a plugin module, it is unwelcome manner to launch its own
worker thread from the module.
If we could map /selinux/status to process memory space, application can
know updates of selinux status; policy reload or setenforce.
A typical application checks selinux_kernel_status::sequence when it tries
to reference userspace avc. If it was changed from the last time when it
checked userspace avc, it means something was updated in the kernel space.
Then, the application can reset userspace avc or update current enforcing
mode, without any system call invocations.
This sequence number is updated according to the seqlock logic, so we need
to wait for a while if it is odd number.
Signed-off-by: KaiGai Kohei <kaigai@ak.jp.nec.com>
Acked-by: Eric Paris <eparis@redhat.com>
--
security/selinux/include/security.h | 21 ++++++
security/selinux/selinuxfs.c | 56 +++++++++++++++
security/selinux/ss/Makefile | 2 +-
security/selinux/ss/services.c | 3 +
security/selinux/ss/status.c | 129 +++++++++++++++++++++++++++++++++++
5 files changed, 210 insertions(+), 1 deletions(-)
Signed-off-by: James Morris <jmorris@namei.org>
2010-09-14 17:28:39 +08:00
|
|
|
|
|
|
|
if (!status)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
filp->private_data = status;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t sel_read_handle_status(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct page *status = filp->private_data;
|
|
|
|
|
|
|
|
BUG_ON(!status);
|
|
|
|
|
|
|
|
return simple_read_from_buffer(buf, count, ppos,
|
|
|
|
page_address(status),
|
|
|
|
sizeof(struct selinux_kernel_status));
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sel_mmap_handle_status(struct file *filp,
|
|
|
|
struct vm_area_struct *vma)
|
|
|
|
{
|
|
|
|
struct page *status = filp->private_data;
|
|
|
|
unsigned long size = vma->vm_end - vma->vm_start;
|
|
|
|
|
|
|
|
BUG_ON(!status);
|
|
|
|
|
|
|
|
/* only allows one page from the head */
|
|
|
|
if (vma->vm_pgoff > 0 || size != PAGE_SIZE)
|
|
|
|
return -EIO;
|
|
|
|
/* disallow writable mapping */
|
|
|
|
if (vma->vm_flags & VM_WRITE)
|
|
|
|
return -EPERM;
|
|
|
|
/* disallow mprotect() turns it into writable */
|
|
|
|
vma->vm_flags &= ~VM_MAYWRITE;
|
|
|
|
|
|
|
|
return remap_pfn_range(vma, vma->vm_start,
|
|
|
|
page_to_pfn(status),
|
|
|
|
size, vma->vm_page_prot);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations sel_handle_status_ops = {
|
|
|
|
.open = sel_open_handle_status,
|
|
|
|
.read = sel_read_handle_status,
|
|
|
|
.mmap = sel_mmap_handle_status,
|
|
|
|
.llseek = generic_file_llseek,
|
|
|
|
};
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_disable(struct file *file, const char __user *buf,
|
2005-04-17 06:20:36 +08:00
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
2015-12-24 13:08:06 +08:00
|
|
|
char *page;
|
2005-04-17 06:20:36 +08:00
|
|
|
ssize_t length;
|
|
|
|
int new_value;
|
2018-04-10 07:34:22 +08:00
|
|
|
int enforcing;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2019-12-19 10:45:08 +08:00
|
|
|
/* NOTE: we are now officially considering runtime disable as
|
|
|
|
* deprecated, and using it will become increasingly painful
|
|
|
|
* (e.g. sleeping/blocking) as we progress through future
|
|
|
|
* kernel releases until eventually it is removed
|
|
|
|
*/
|
|
|
|
pr_err("SELinux: Runtime disable is deprecated, use selinux=0 on the kernel cmdline.\n");
|
|
|
|
|
2005-10-31 06:59:24 +08:00
|
|
|
if (count >= PAGE_SIZE)
|
2015-12-24 13:08:06 +08:00
|
|
|
return -ENOMEM;
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
/* No partial writes. */
|
|
|
|
if (*ppos != 0)
|
2015-12-24 13:08:06 +08:00
|
|
|
return -EINVAL;
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2015-12-24 13:08:06 +08:00
|
|
|
page = memdup_user_nul(buf, count);
|
|
|
|
if (IS_ERR(page))
|
|
|
|
return PTR_ERR(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = -EINVAL;
|
|
|
|
if (sscanf(page, "%d", &new_value) != 1)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (new_value) {
|
2018-04-10 07:34:22 +08:00
|
|
|
enforcing = enforcing_enabled(fsi->state);
|
2018-03-20 23:59:11 +08:00
|
|
|
length = selinux_disable(fsi->state);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
2018-05-13 09:58:20 +08:00
|
|
|
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
|
2018-04-10 07:34:22 +08:00
|
|
|
"enforcing=%d old_enforcing=%d auid=%u ses=%u"
|
2019-12-17 22:15:10 +08:00
|
|
|
" enabled=0 old-enabled=1 lsm=selinux res=1",
|
2018-04-10 07:34:22 +08:00
|
|
|
enforcing, enforcing,
|
2012-08-20 15:09:36 +08:00
|
|
|
from_kuid(&init_user_ns, audit_get_loginuid(current)),
|
2019-12-17 22:15:10 +08:00
|
|
|
audit_get_sessionid(current));
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
length = count;
|
|
|
|
out:
|
2015-12-24 13:08:06 +08:00
|
|
|
kfree(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
return length;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
#define sel_write_disable NULL
|
|
|
|
#endif
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_disable_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.write = sel_write_disable,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static ssize_t sel_read_policyvers(struct file *filp, char __user *buf,
|
2008-04-18 02:15:45 +08:00
|
|
|
size_t count, loff_t *ppos)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
|
|
|
char tmpbuf[TMPBUFLEN];
|
|
|
|
ssize_t length;
|
|
|
|
|
|
|
|
length = scnprintf(tmpbuf, TMPBUFLEN, "%u", POLICYDB_VERSION_MAX);
|
|
|
|
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
|
|
|
|
}
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_policyvers_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.read = sel_read_policyvers,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/* declaration for sel_write_load */
|
2020-08-20 03:59:33 +08:00
|
|
|
static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_dir,
|
|
|
|
unsigned int *bool_num, char ***bool_pending_names,
|
|
|
|
unsigned int **bool_pending_values);
|
|
|
|
static int sel_make_classes(struct selinux_policy *newpolicy,
|
|
|
|
struct dentry *class_dir,
|
|
|
|
unsigned long *last_class_ino);
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
/* declaration for sel_make_class_dirs */
|
2012-03-19 08:36:59 +08:00
|
|
|
static struct dentry *sel_make_dir(struct dentry *dir, const char *name,
|
2007-05-23 21:12:09 +08:00
|
|
|
unsigned long *ino);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
/* declaration for sel_make_policy_nodes */
|
|
|
|
static struct dentry *sel_make_disconnected_dir(struct super_block *sb,
|
|
|
|
unsigned long *ino);
|
|
|
|
|
|
|
|
/* declaration for sel_make_policy_nodes */
|
2020-08-20 03:59:32 +08:00
|
|
|
static void sel_remove_entries(struct dentry *de);
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static ssize_t sel_read_mls(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;
|
2005-04-17 06:20:36 +08:00
|
|
|
char tmpbuf[TMPBUFLEN];
|
|
|
|
ssize_t length;
|
|
|
|
|
2010-02-03 23:40:20 +08:00
|
|
|
length = scnprintf(tmpbuf, TMPBUFLEN, "%d",
|
2018-03-20 23:59:11 +08:00
|
|
|
security_mls_enabled(fsi->state));
|
2005-04-17 06:20:36 +08:00
|
|
|
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
|
|
|
|
}
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_mls_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.read = sel_read_mls,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2010-10-14 05:50:25 +08:00
|
|
|
struct policy_load_memory {
|
|
|
|
size_t len;
|
|
|
|
void *data;
|
|
|
|
};
|
|
|
|
|
|
|
|
static int sel_open_policy(struct inode *inode, struct file *filp)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = inode->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2010-10-14 05:50:25 +08:00
|
|
|
struct policy_load_memory *plm = NULL;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
BUG_ON(filp->private_data);
|
|
|
|
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_lock(&fsi->state->policy_mutex);
|
2010-10-14 05:50:25 +08:00
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
rc = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__READ_POLICY, NULL);
|
2010-10-14 05:50:25 +08:00
|
|
|
if (rc)
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
rc = -EBUSY;
|
2018-03-20 23:59:11 +08:00
|
|
|
if (fsi->policy_opened)
|
2010-10-14 05:50:25 +08:00
|
|
|
goto err;
|
|
|
|
|
|
|
|
rc = -ENOMEM;
|
|
|
|
plm = kzalloc(sizeof(*plm), GFP_KERNEL);
|
|
|
|
if (!plm)
|
|
|
|
goto err;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
rc = security_read_policy(state, &plm->data, &plm->len);
|
2010-10-14 05:50:25 +08:00
|
|
|
if (rc)
|
|
|
|
goto err;
|
|
|
|
|
2020-08-28 00:27:53 +08:00
|
|
|
if ((size_t)i_size_read(inode) != plm->len) {
|
|
|
|
inode_lock(inode);
|
|
|
|
i_size_write(inode, plm->len);
|
|
|
|
inode_unlock(inode);
|
|
|
|
}
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
fsi->policy_opened = 1;
|
2010-10-14 05:50:25 +08:00
|
|
|
|
|
|
|
filp->private_data = plm;
|
|
|
|
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_unlock(&fsi->state->policy_mutex);
|
2010-10-14 05:50:25 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
err:
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_unlock(&fsi->state->policy_mutex);
|
2010-10-14 05:50:25 +08:00
|
|
|
|
|
|
|
if (plm)
|
|
|
|
vfree(plm->data);
|
|
|
|
kfree(plm);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sel_release_policy(struct inode *inode, struct file *filp)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = inode->i_sb->s_fs_info;
|
2010-10-14 05:50:25 +08:00
|
|
|
struct policy_load_memory *plm = filp->private_data;
|
|
|
|
|
|
|
|
BUG_ON(!plm);
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
fsi->policy_opened = 0;
|
2010-10-14 05:50:25 +08:00
|
|
|
|
|
|
|
vfree(plm->data);
|
|
|
|
kfree(plm);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t sel_read_policy(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct policy_load_memory *plm = filp->private_data;
|
|
|
|
int ret;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
ret = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__READ_POLICY, NULL);
|
2010-10-14 05:50:25 +08:00
|
|
|
if (ret)
|
2018-06-29 08:39:54 +08:00
|
|
|
return ret;
|
2010-10-14 05:50:25 +08:00
|
|
|
|
2018-06-29 08:39:54 +08:00
|
|
|
return simple_read_from_buffer(buf, count, ppos, plm->data, plm->len);
|
2010-10-14 05:50:25 +08:00
|
|
|
}
|
|
|
|
|
2018-04-14 23:32:41 +08:00
|
|
|
static vm_fault_t sel_mmap_policy_fault(struct vm_fault *vmf)
|
2010-10-14 05:50:31 +08:00
|
|
|
{
|
2017-02-25 06:56:41 +08:00
|
|
|
struct policy_load_memory *plm = vmf->vma->vm_file->private_data;
|
2010-10-14 05:50:31 +08:00
|
|
|
unsigned long offset;
|
|
|
|
struct page *page;
|
|
|
|
|
|
|
|
if (vmf->flags & (FAULT_FLAG_MKWRITE | FAULT_FLAG_WRITE))
|
|
|
|
return VM_FAULT_SIGBUS;
|
|
|
|
|
|
|
|
offset = vmf->pgoff << PAGE_SHIFT;
|
|
|
|
if (offset >= roundup(plm->len, PAGE_SIZE))
|
|
|
|
return VM_FAULT_SIGBUS;
|
|
|
|
|
|
|
|
page = vmalloc_to_page(plm->data + offset);
|
|
|
|
get_page(page);
|
|
|
|
|
|
|
|
vmf->page = page;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-09-10 06:39:26 +08:00
|
|
|
static const struct vm_operations_struct sel_mmap_policy_ops = {
|
2010-10-14 05:50:31 +08:00
|
|
|
.fault = sel_mmap_policy_fault,
|
|
|
|
.page_mkwrite = sel_mmap_policy_fault,
|
|
|
|
};
|
|
|
|
|
2011-08-30 08:50:12 +08:00
|
|
|
static int sel_mmap_policy(struct file *filp, struct vm_area_struct *vma)
|
2010-10-14 05:50:31 +08:00
|
|
|
{
|
|
|
|
if (vma->vm_flags & VM_SHARED) {
|
|
|
|
/* do not allow mprotect to make mapping writable */
|
|
|
|
vma->vm_flags &= ~VM_MAYWRITE;
|
|
|
|
|
|
|
|
if (vma->vm_flags & VM_WRITE)
|
|
|
|
return -EACCES;
|
|
|
|
}
|
|
|
|
|
mm: kill vma flag VM_RESERVED and mm->reserved_vm counter
A long time ago, in v2.4, VM_RESERVED kept swapout process off VMA,
currently it lost original meaning but still has some effects:
| effect | alternative flags
-+------------------------+---------------------------------------------
1| account as reserved_vm | VM_IO
2| skip in core dump | VM_IO, VM_DONTDUMP
3| do not merge or expand | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP
4| do not mlock | VM_IO, VM_DONTEXPAND, VM_HUGETLB, VM_PFNMAP
This patch removes reserved_vm counter from mm_struct. Seems like nobody
cares about it, it does not exported into userspace directly, it only
reduces total_vm showed in proc.
Thus VM_RESERVED can be replaced with VM_IO or pair VM_DONTEXPAND | VM_DONTDUMP.
remap_pfn_range() and io_remap_pfn_range() set VM_IO|VM_DONTEXPAND|VM_DONTDUMP.
remap_vmalloc_range() set VM_DONTEXPAND | VM_DONTDUMP.
[akpm@linux-foundation.org: drivers/vfio/pci/vfio_pci.c fixup]
Signed-off-by: Konstantin Khlebnikov <khlebnikov@openvz.org>
Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Cc: Carsten Otte <cotte@de.ibm.com>
Cc: Chris Metcalf <cmetcalf@tilera.com>
Cc: Cyrill Gorcunov <gorcunov@openvz.org>
Cc: Eric Paris <eparis@redhat.com>
Cc: H. Peter Anvin <hpa@zytor.com>
Cc: Hugh Dickins <hughd@google.com>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: James Morris <james.l.morris@oracle.com>
Cc: Jason Baron <jbaron@redhat.com>
Cc: Kentaro Takeda <takedakn@nttdata.co.jp>
Cc: Matt Helsley <matthltc@us.ibm.com>
Cc: Nick Piggin <npiggin@kernel.dk>
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Robert Richter <robert.richter@amd.com>
Cc: Suresh Siddha <suresh.b.siddha@intel.com>
Cc: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Cc: Venkatesh Pallipadi <venki@google.com>
Acked-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2012-10-09 07:29:02 +08:00
|
|
|
vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
|
2010-10-14 05:50:31 +08:00
|
|
|
vma->vm_ops = &sel_mmap_policy_ops;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2010-10-14 05:50:25 +08:00
|
|
|
static const struct file_operations sel_policy_ops = {
|
|
|
|
.open = sel_open_policy,
|
|
|
|
.read = sel_read_policy,
|
2010-10-14 05:50:31 +08:00
|
|
|
.mmap = sel_mmap_policy,
|
2010-10-14 05:50:25 +08:00
|
|
|
.release = sel_release_policy,
|
2012-02-17 04:08:39 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2010-10-14 05:50:25 +08:00
|
|
|
};
|
|
|
|
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
static void sel_remove_old_bool_data(unsigned int bool_num, char **bool_names,
|
|
|
|
unsigned int *bool_values)
|
2020-08-20 03:59:32 +08:00
|
|
|
{
|
|
|
|
u32 i;
|
|
|
|
|
|
|
|
/* bool_dir cleanup */
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
for (i = 0; i < bool_num; i++)
|
|
|
|
kfree(bool_names[i]);
|
|
|
|
kfree(bool_names);
|
|
|
|
kfree(bool_values);
|
2020-08-20 03:59:32 +08:00
|
|
|
}
|
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
|
|
|
|
struct selinux_policy *newpolicy)
|
2018-03-20 23:59:11 +08:00
|
|
|
{
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
int ret = 0;
|
|
|
|
struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir, *old_dentry;
|
|
|
|
unsigned int tmp_bool_num, old_bool_num;
|
|
|
|
char **tmp_bool_names, **old_bool_names;
|
|
|
|
unsigned int *tmp_bool_values, *old_bool_values;
|
|
|
|
unsigned long tmp_ino = fsi->last_ino; /* Don't increment last_ino in this function */
|
|
|
|
|
|
|
|
tmp_parent = sel_make_disconnected_dir(fsi->sb, &tmp_ino);
|
|
|
|
if (IS_ERR(tmp_parent))
|
|
|
|
return PTR_ERR(tmp_parent);
|
|
|
|
|
|
|
|
tmp_ino = fsi->bool_dir->d_inode->i_ino - 1; /* sel_make_dir will increment and set */
|
|
|
|
tmp_bool_dir = sel_make_dir(tmp_parent, BOOL_DIR_NAME, &tmp_ino);
|
|
|
|
if (IS_ERR(tmp_bool_dir)) {
|
|
|
|
ret = PTR_ERR(tmp_bool_dir);
|
|
|
|
goto out;
|
|
|
|
}
|
2018-03-20 23:59:11 +08:00
|
|
|
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
tmp_ino = fsi->class_dir->d_inode->i_ino - 1; /* sel_make_dir will increment and set */
|
|
|
|
tmp_class_dir = sel_make_dir(tmp_parent, CLASS_DIR_NAME, &tmp_ino);
|
|
|
|
if (IS_ERR(tmp_class_dir)) {
|
|
|
|
ret = PTR_ERR(tmp_class_dir);
|
|
|
|
goto out;
|
|
|
|
}
|
2020-08-20 03:59:32 +08:00
|
|
|
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
ret = sel_make_bools(newpolicy, tmp_bool_dir, &tmp_bool_num,
|
|
|
|
&tmp_bool_names, &tmp_bool_values);
|
2018-03-20 23:59:11 +08:00
|
|
|
if (ret) {
|
|
|
|
pr_err("SELinux: failed to load policy booleans\n");
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
goto out;
|
2018-03-20 23:59:11 +08:00
|
|
|
}
|
|
|
|
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
ret = sel_make_classes(newpolicy, tmp_class_dir,
|
2020-08-20 03:59:33 +08:00
|
|
|
&fsi->last_class_ino);
|
2018-03-20 23:59:11 +08:00
|
|
|
if (ret) {
|
|
|
|
pr_err("SELinux: failed to load policy classes\n");
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
goto out;
|
2018-03-20 23:59:11 +08:00
|
|
|
}
|
|
|
|
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
/* booleans */
|
|
|
|
old_dentry = fsi->bool_dir;
|
|
|
|
lock_rename(tmp_bool_dir, old_dentry);
|
|
|
|
d_exchange(tmp_bool_dir, fsi->bool_dir);
|
|
|
|
|
|
|
|
old_bool_num = fsi->bool_num;
|
|
|
|
old_bool_names = fsi->bool_pending_names;
|
|
|
|
old_bool_values = fsi->bool_pending_values;
|
|
|
|
|
|
|
|
fsi->bool_num = tmp_bool_num;
|
|
|
|
fsi->bool_pending_names = tmp_bool_names;
|
|
|
|
fsi->bool_pending_values = tmp_bool_values;
|
|
|
|
|
|
|
|
sel_remove_old_bool_data(old_bool_num, old_bool_names, old_bool_values);
|
|
|
|
|
|
|
|
fsi->bool_dir = tmp_bool_dir;
|
|
|
|
unlock_rename(tmp_bool_dir, old_dentry);
|
|
|
|
|
|
|
|
/* classes */
|
|
|
|
old_dentry = fsi->class_dir;
|
|
|
|
lock_rename(tmp_class_dir, old_dentry);
|
|
|
|
d_exchange(tmp_class_dir, fsi->class_dir);
|
|
|
|
fsi->class_dir = tmp_class_dir;
|
|
|
|
unlock_rename(tmp_class_dir, old_dentry);
|
|
|
|
|
|
|
|
out:
|
|
|
|
/* Since the other temporary dirs are children of tmp_parent
|
|
|
|
* this will handle all the cleanup in the case of a failure before
|
|
|
|
* the swapover
|
|
|
|
*/
|
|
|
|
sel_remove_entries(tmp_parent);
|
|
|
|
dput(tmp_parent); /* d_genocide() only handles the children */
|
|
|
|
|
|
|
|
return ret;
|
2018-03-20 23:59:11 +08:00
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_load(struct file *file, const char __user *buf,
|
2005-04-17 06:20:36 +08:00
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
2020-08-07 21:29:34 +08:00
|
|
|
struct selinux_policy *newpolicy;
|
2005-04-17 06:20:36 +08:00
|
|
|
ssize_t length;
|
|
|
|
void *data = NULL;
|
|
|
|
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_lock(&fsi->state->policy_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__LOAD_POLICY, NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
/* No partial writes. */
|
|
|
|
length = -EINVAL;
|
|
|
|
if (*ppos != 0)
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ENOMEM;
|
|
|
|
data = vmalloc(count);
|
|
|
|
if (!data)
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
length = -EFAULT;
|
|
|
|
if (copy_from_user(data, buf, count) != 0)
|
|
|
|
goto out;
|
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
length = security_load_policy(fsi->state, data, count, &newpolicy);
|
2017-01-09 23:07:31 +08:00
|
|
|
if (length) {
|
|
|
|
pr_warn_ratelimited("SELinux: failed to load policy\n");
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
2017-01-09 23:07:31 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
length = sel_make_policy_nodes(fsi, newpolicy);
|
|
|
|
if (length) {
|
|
|
|
selinux_policy_cancel(fsi->state, newpolicy);
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out1;
|
2020-08-07 21:29:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
selinux_policy_commit(fsi->state, newpolicy);
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
length = count;
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
out1:
|
2018-05-13 09:58:20 +08:00
|
|
|
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_POLICY_LOAD,
|
2018-04-10 07:36:31 +08:00
|
|
|
"auid=%u ses=%u lsm=selinux res=1",
|
2012-08-20 15:09:36 +08:00
|
|
|
from_kuid(&init_user_ns, audit_get_loginuid(current)),
|
2008-01-08 23:06:53 +08:00
|
|
|
audit_get_sessionid(current));
|
2005-04-17 06:20:36 +08:00
|
|
|
out:
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_unlock(&fsi->state->policy_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
vfree(data);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_load_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.write = sel_write_load,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_context(struct file *file, char *buf, size_t size)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *canon = NULL;
|
2005-11-09 13:34:33 +08:00
|
|
|
u32 sid, len;
|
2005-04-17 06:20:36 +08:00
|
|
|
ssize_t length;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__CHECK_CONTEXT, NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_to_sid(state, buf, size, &sid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_sid_to_context(state, sid, &canon, &len);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-11-09 13:34:33 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ERANGE;
|
2005-11-09 13:34:33 +08:00
|
|
|
if (len > SIMPLE_TRANSACTION_LIMIT) {
|
2018-06-12 16:09:06 +08:00
|
|
|
pr_err("SELinux: %s: context size (%u) exceeds "
|
2008-04-17 23:52:44 +08:00
|
|
|
"payload max\n", __func__, len);
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
2005-11-09 13:34:33 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-11-09 13:34:33 +08:00
|
|
|
memcpy(buf, canon, len);
|
|
|
|
length = len;
|
2005-04-17 06:20:36 +08:00
|
|
|
out:
|
2005-11-09 13:34:33 +08:00
|
|
|
kfree(canon);
|
2005-04-17 06:20:36 +08:00
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;
|
2005-04-17 06:20:36 +08:00
|
|
|
char tmpbuf[TMPBUFLEN];
|
|
|
|
ssize_t length;
|
|
|
|
|
2020-09-15 01:31:57 +08:00
|
|
|
length = scnprintf(tmpbuf, TMPBUFLEN, "%u",
|
|
|
|
checkreqprot_get(fsi->state));
|
2005-04-17 06:20:36 +08:00
|
|
|
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
|
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf,
|
2005-04-17 06:20:36 +08:00
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
2015-12-24 13:08:06 +08:00
|
|
|
char *page;
|
2005-04-17 06:20:36 +08:00
|
|
|
ssize_t length;
|
|
|
|
unsigned int new_value;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__SETCHECKREQPROT,
|
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
2015-12-24 13:08:06 +08:00
|
|
|
return length;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2005-10-31 06:59:24 +08:00
|
|
|
if (count >= PAGE_SIZE)
|
2015-12-24 13:08:06 +08:00
|
|
|
return -ENOMEM;
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
/* No partial writes. */
|
|
|
|
if (*ppos != 0)
|
2015-12-24 13:08:06 +08:00
|
|
|
return -EINVAL;
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2015-12-24 13:08:06 +08:00
|
|
|
page = memdup_user_nul(buf, count);
|
|
|
|
if (IS_ERR(page))
|
|
|
|
return PTR_ERR(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = -EINVAL;
|
|
|
|
if (sscanf(page, "%u", &new_value) != 1)
|
|
|
|
goto out;
|
|
|
|
|
2020-01-09 00:24:47 +08:00
|
|
|
if (new_value) {
|
|
|
|
char comm[sizeof(current->comm)];
|
|
|
|
|
|
|
|
memcpy(comm, current->comm, sizeof(comm));
|
|
|
|
pr_warn_once("SELinux: %s (%d) set checkreqprot to 1. This is deprecated and will be rejected in a future kernel release.\n",
|
|
|
|
comm, current->pid);
|
|
|
|
}
|
|
|
|
|
2020-09-15 01:31:57 +08:00
|
|
|
checkreqprot_set(fsi->state, (new_value ? 1 : 0));
|
2005-04-17 06:20:36 +08:00
|
|
|
length = count;
|
|
|
|
out:
|
2015-12-24 13:08:06 +08:00
|
|
|
kfree(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
return length;
|
|
|
|
}
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_checkreqprot_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.read = sel_read_checkreqprot,
|
|
|
|
.write = sel_write_checkreqprot,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2015-12-25 00:09:41 +08:00
|
|
|
static ssize_t sel_write_validatetrans(struct file *file,
|
|
|
|
const char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2015-12-25 00:09:41 +08:00
|
|
|
char *oldcon = NULL, *newcon = NULL, *taskcon = NULL;
|
|
|
|
char *req = NULL;
|
|
|
|
u32 osid, nsid, tsid;
|
|
|
|
u16 tclass;
|
|
|
|
int rc;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
rc = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__VALIDATE_TRANS, NULL);
|
2015-12-25 00:09:41 +08:00
|
|
|
if (rc)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
rc = -ENOMEM;
|
|
|
|
if (count >= PAGE_SIZE)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
/* No partial writes. */
|
|
|
|
rc = -EINVAL;
|
|
|
|
if (*ppos != 0)
|
|
|
|
goto out;
|
|
|
|
|
2017-05-14 06:12:07 +08:00
|
|
|
req = memdup_user_nul(buf, count);
|
|
|
|
if (IS_ERR(req)) {
|
|
|
|
rc = PTR_ERR(req);
|
|
|
|
req = NULL;
|
2015-12-25 00:09:41 +08:00
|
|
|
goto out;
|
2017-05-14 06:12:07 +08:00
|
|
|
}
|
2015-12-25 00:09:41 +08:00
|
|
|
|
|
|
|
rc = -ENOMEM;
|
|
|
|
oldcon = kzalloc(count + 1, GFP_KERNEL);
|
|
|
|
if (!oldcon)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
newcon = kzalloc(count + 1, GFP_KERNEL);
|
|
|
|
if (!newcon)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
taskcon = kzalloc(count + 1, GFP_KERNEL);
|
|
|
|
if (!taskcon)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
rc = -EINVAL;
|
|
|
|
if (sscanf(req, "%s %s %hu %s", oldcon, newcon, &tclass, taskcon) != 4)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
rc = security_context_str_to_sid(state, oldcon, &osid, GFP_KERNEL);
|
2015-12-25 00:09:41 +08:00
|
|
|
if (rc)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
rc = security_context_str_to_sid(state, newcon, &nsid, GFP_KERNEL);
|
2015-12-25 00:09:41 +08:00
|
|
|
if (rc)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
rc = security_context_str_to_sid(state, taskcon, &tsid, GFP_KERNEL);
|
2015-12-25 00:09:41 +08:00
|
|
|
if (rc)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
rc = security_validate_transition_user(state, osid, nsid, tsid, tclass);
|
2015-12-25 00:09:41 +08:00
|
|
|
if (!rc)
|
|
|
|
rc = count;
|
|
|
|
out:
|
|
|
|
kfree(req);
|
|
|
|
kfree(oldcon);
|
|
|
|
kfree(newcon);
|
|
|
|
kfree(taskcon);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations sel_transition_ops = {
|
|
|
|
.write = sel_write_validatetrans,
|
|
|
|
.llseek = generic_file_llseek,
|
|
|
|
};
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* Remaining nodes use transaction based IO methods like nfsd/nfsctl.c
|
|
|
|
*/
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_access(struct file *file, char *buf, size_t size);
|
|
|
|
static ssize_t sel_write_create(struct file *file, char *buf, size_t size);
|
|
|
|
static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size);
|
|
|
|
static ssize_t sel_write_user(struct file *file, char *buf, size_t size);
|
|
|
|
static ssize_t sel_write_member(struct file *file, char *buf, size_t size);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-07-18 01:43:56 +08:00
|
|
|
static ssize_t (*const write_op[])(struct file *, char *, size_t) = {
|
2005-04-17 06:20:36 +08:00
|
|
|
[SEL_ACCESS] = sel_write_access,
|
|
|
|
[SEL_CREATE] = sel_write_create,
|
|
|
|
[SEL_RELABEL] = sel_write_relabel,
|
|
|
|
[SEL_USER] = sel_write_user,
|
|
|
|
[SEL_MEMBER] = sel_write_member,
|
2005-11-09 13:34:33 +08:00
|
|
|
[SEL_CONTEXT] = sel_write_context,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static ssize_t selinux_transaction_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
|
|
|
|
{
|
2013-01-24 06:07:38 +08:00
|
|
|
ino_t ino = file_inode(file)->i_ino;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *data;
|
|
|
|
ssize_t rv;
|
|
|
|
|
2006-01-06 16:11:22 +08:00
|
|
|
if (ino >= ARRAY_SIZE(write_op) || !write_op[ino])
|
2005-04-17 06:20:36 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
data = simple_transaction_get(file, buf, size);
|
|
|
|
if (IS_ERR(data))
|
|
|
|
return PTR_ERR(data);
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
rv = write_op[ino](file, data, size);
|
|
|
|
if (rv > 0) {
|
2005-04-17 06:20:36 +08:00
|
|
|
simple_transaction_set(file, rv);
|
|
|
|
rv = size;
|
|
|
|
}
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations transaction_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.write = selinux_transaction_write,
|
|
|
|
.read = simple_transaction_read,
|
|
|
|
.release = simple_transaction_release,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/*
|
|
|
|
* payload - write methods
|
|
|
|
* If the method has a response, the response should be put in buf,
|
|
|
|
* and the length returned. Otherwise return 0 or and -error.
|
|
|
|
*/
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_access(struct file *file, char *buf, size_t size)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *scon = NULL, *tcon = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
u32 ssid, tsid;
|
|
|
|
u16 tclass;
|
|
|
|
struct av_decision avd;
|
|
|
|
ssize_t length;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__COMPUTE_AV, NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
scon = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!scon)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
tcon = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!tcon)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
length = -EINVAL;
|
2010-01-15 06:28:10 +08:00
|
|
|
if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, scon, &ssid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, tcon, &tsid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
security_compute_av_user(state, ssid, tsid, tclass, &avd);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = scnprintf(buf, SIMPLE_TRANSACTION_LIMIT,
|
Permissive domain in userspace object manager
This patch enables applications to handle permissive domain correctly.
Since the v2.6.26 kernel, SELinux has supported an idea of permissive
domain which allows certain processes to work as if permissive mode,
even if the global setting is enforcing mode.
However, we don't have an application program interface to inform
what domains are permissive one, and what domains are not.
It means applications focuses on SELinux (XACE/SELinux, SE-PostgreSQL
and so on) cannot handle permissive domain correctly.
This patch add the sixth field (flags) on the reply of the /selinux/access
interface which is used to make an access control decision from userspace.
If the first bit of the flags field is positive, it means the required
access control decision is on permissive domain, so application should
allow any required actions, as the kernel doing.
This patch also has a side benefit. The av_decision.flags is set at
context_struct_compute_av(). It enables to check required permissions
without read_lock(&policy_rwlock).
Signed-off-by: KaiGai Kohei <kaigai@ak.jp.nec.com>
Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
Acked-by: Eric Paris <eparis@redhat.com>
--
security/selinux/avc.c | 2 +-
security/selinux/include/security.h | 4 +++-
security/selinux/selinuxfs.c | 4 ++--
security/selinux/ss/services.c | 30 +++++-------------------------
4 files changed, 11 insertions(+), 29 deletions(-)
Signed-off-by: James Morris <jmorris@namei.org>
2009-04-01 09:07:57 +08:00
|
|
|
"%x %x %x %x %u %x",
|
2009-02-13 03:50:54 +08:00
|
|
|
avd.allowed, 0xffffffff,
|
2005-04-17 06:20:36 +08:00
|
|
|
avd.auditallow, avd.auditdeny,
|
Permissive domain in userspace object manager
This patch enables applications to handle permissive domain correctly.
Since the v2.6.26 kernel, SELinux has supported an idea of permissive
domain which allows certain processes to work as if permissive mode,
even if the global setting is enforcing mode.
However, we don't have an application program interface to inform
what domains are permissive one, and what domains are not.
It means applications focuses on SELinux (XACE/SELinux, SE-PostgreSQL
and so on) cannot handle permissive domain correctly.
This patch add the sixth field (flags) on the reply of the /selinux/access
interface which is used to make an access control decision from userspace.
If the first bit of the flags field is positive, it means the required
access control decision is on permissive domain, so application should
allow any required actions, as the kernel doing.
This patch also has a side benefit. The av_decision.flags is set at
context_struct_compute_av(). It enables to check required permissions
without read_lock(&policy_rwlock).
Signed-off-by: KaiGai Kohei <kaigai@ak.jp.nec.com>
Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
Acked-by: Eric Paris <eparis@redhat.com>
--
security/selinux/avc.c | 2 +-
security/selinux/include/security.h | 4 +++-
security/selinux/selinuxfs.c | 4 ++--
security/selinux/ss/services.c | 30 +++++-------------------------
4 files changed, 11 insertions(+), 29 deletions(-)
Signed-off-by: James Morris <jmorris@namei.org>
2009-04-01 09:07:57 +08:00
|
|
|
avd.seqno, avd.flags);
|
2005-04-17 06:20:36 +08:00
|
|
|
out:
|
2010-11-24 00:40:08 +08:00
|
|
|
kfree(tcon);
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(scon);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_create(struct file *file, char *buf, size_t size)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *scon = NULL, *tcon = NULL;
|
2011-04-01 22:39:26 +08:00
|
|
|
char *namebuf = NULL, *objname = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
u32 ssid, tsid, newsid;
|
|
|
|
u16 tclass;
|
|
|
|
ssize_t length;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *newcon = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
u32 len;
|
2011-04-01 22:39:26 +08:00
|
|
|
int nargs;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__COMPUTE_CREATE,
|
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
scon = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!scon)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
tcon = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!tcon)
|
|
|
|
goto out;
|
|
|
|
|
2011-04-01 22:39:26 +08:00
|
|
|
length = -ENOMEM;
|
|
|
|
namebuf = kzalloc(size + 1, GFP_KERNEL);
|
|
|
|
if (!namebuf)
|
|
|
|
goto out;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
length = -EINVAL;
|
2011-04-01 22:39:26 +08:00
|
|
|
nargs = sscanf(buf, "%s %s %hu %s", scon, tcon, &tclass, namebuf);
|
|
|
|
if (nargs < 3 || nargs > 4)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2011-05-27 02:59:25 +08:00
|
|
|
if (nargs == 4) {
|
|
|
|
/*
|
|
|
|
* If and when the name of new object to be queried contains
|
|
|
|
* either whitespace or multibyte characters, they shall be
|
|
|
|
* encoded based on the percentage-encoding rule.
|
|
|
|
* If not encoded, the sscanf logic picks up only left-half
|
|
|
|
* of the supplied name; splitted by a whitespace unexpectedly.
|
|
|
|
*/
|
|
|
|
char *r, *w;
|
|
|
|
int c1, c2;
|
|
|
|
|
|
|
|
r = w = namebuf;
|
|
|
|
do {
|
|
|
|
c1 = *r++;
|
|
|
|
if (c1 == '+')
|
|
|
|
c1 = ' ';
|
|
|
|
else if (c1 == '%') {
|
2011-11-16 07:11:41 +08:00
|
|
|
c1 = hex_to_bin(*r++);
|
|
|
|
if (c1 < 0)
|
2011-05-27 02:59:25 +08:00
|
|
|
goto out;
|
2011-11-16 07:11:41 +08:00
|
|
|
c2 = hex_to_bin(*r++);
|
|
|
|
if (c2 < 0)
|
2011-05-27 02:59:25 +08:00
|
|
|
goto out;
|
|
|
|
c1 = (c1 << 4) | c2;
|
|
|
|
}
|
|
|
|
*w++ = c1;
|
|
|
|
} while (c1 != '\0');
|
|
|
|
|
2011-04-01 22:39:26 +08:00
|
|
|
objname = namebuf;
|
2011-05-27 02:59:25 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, scon, &ssid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, tcon, &tsid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_transition_sid_user(state, ssid, tsid, tclass,
|
|
|
|
objname, &newsid);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_sid_to_context(state, newsid, &newcon, &len);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ERANGE;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (len > SIMPLE_TRANSACTION_LIMIT) {
|
2018-06-12 16:09:06 +08:00
|
|
|
pr_err("SELinux: %s: context size (%u) exceeds "
|
2008-04-17 23:52:44 +08:00
|
|
|
"payload max\n", __func__, len);
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(buf, newcon, len);
|
|
|
|
length = len;
|
2010-11-24 00:40:08 +08:00
|
|
|
out:
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(newcon);
|
2011-04-01 22:39:26 +08:00
|
|
|
kfree(namebuf);
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(tcon);
|
|
|
|
kfree(scon);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_relabel(struct file *file, char *buf, size_t size)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *scon = NULL, *tcon = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
u32 ssid, tsid, newsid;
|
|
|
|
u16 tclass;
|
|
|
|
ssize_t length;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *newcon = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
u32 len;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__COMPUTE_RELABEL,
|
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
scon = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!scon)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
tcon = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!tcon)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
length = -EINVAL;
|
|
|
|
if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, scon, &ssid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, tcon, &tsid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_change_sid(state, ssid, tsid, tclass, &newsid);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_sid_to_context(state, newsid, &newcon, &len);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ERANGE;
|
|
|
|
if (len > SIMPLE_TRANSACTION_LIMIT)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
memcpy(buf, newcon, len);
|
|
|
|
length = len;
|
2010-11-24 00:40:08 +08:00
|
|
|
out:
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(newcon);
|
|
|
|
kfree(tcon);
|
|
|
|
kfree(scon);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_user(struct file *file, char *buf, size_t size)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *con = NULL, *user = NULL, *ptr;
|
|
|
|
u32 sid, *sids = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
ssize_t length;
|
|
|
|
char *newcon;
|
|
|
|
int i, rc;
|
|
|
|
u32 len, nsids;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__COMPUTE_USER,
|
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
2011-04-09 10:49:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
con = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!con)
|
2011-04-09 10:49:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
user = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!user)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
length = -EINVAL;
|
|
|
|
if (sscanf(buf, "%s %s", con, user) != 2)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, con, &sid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_get_user_sids(state, sid, user, &sids, &nsids);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = sprintf(buf, "%u", nsids) + 1;
|
|
|
|
ptr = buf + length;
|
|
|
|
for (i = 0; i < nsids; i++) {
|
2018-03-20 23:59:11 +08:00
|
|
|
rc = security_sid_to_context(state, sids[i], &newcon, &len);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (rc) {
|
|
|
|
length = rc;
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
if ((length + len) >= SIMPLE_TRANSACTION_LIMIT) {
|
|
|
|
kfree(newcon);
|
|
|
|
length = -ERANGE;
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
memcpy(ptr, newcon, len);
|
|
|
|
kfree(newcon);
|
|
|
|
ptr += len;
|
|
|
|
length += len;
|
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
out:
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(sids);
|
|
|
|
kfree(user);
|
|
|
|
kfree(con);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_member(struct file *file, char *buf, size_t size)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *scon = NULL, *tcon = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
u32 ssid, tsid, newsid;
|
|
|
|
u16 tclass;
|
|
|
|
ssize_t length;
|
2010-11-24 00:40:08 +08:00
|
|
|
char *newcon = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
u32 len;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__COMPUTE_MEMBER,
|
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
scon = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!scon)
|
2011-04-09 10:49:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ENOMEM;
|
2010-04-09 19:30:29 +08:00
|
|
|
tcon = kzalloc(size + 1, GFP_KERNEL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!tcon)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
length = -EINVAL;
|
|
|
|
if (sscanf(buf, "%s %s %hu", scon, tcon, &tclass) != 3)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, scon, &ssid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_context_str_to_sid(state, tcon, &tsid, GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_member_sid(state, ssid, tsid, tclass, &newsid);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
length = security_sid_to_context(state, newsid, &newcon, &len);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -ERANGE;
|
2005-04-17 06:20:36 +08:00
|
|
|
if (len > SIMPLE_TRANSACTION_LIMIT) {
|
2018-06-12 16:09:06 +08:00
|
|
|
pr_err("SELinux: %s: context size (%u) exceeds "
|
2008-04-17 23:52:44 +08:00
|
|
|
"payload max\n", __func__, len);
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
memcpy(buf, newcon, len);
|
|
|
|
length = len;
|
2010-11-24 00:40:08 +08:00
|
|
|
out:
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(newcon);
|
|
|
|
kfree(tcon);
|
|
|
|
kfree(scon);
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct inode *sel_make_inode(struct super_block *sb, int mode)
|
|
|
|
{
|
|
|
|
struct inode *ret = new_inode(sb);
|
|
|
|
|
|
|
|
if (ret) {
|
|
|
|
ret->i_mode = mode;
|
2016-09-14 22:48:04 +08:00
|
|
|
ret->i_atime = ret->i_mtime = ret->i_ctime = current_time(ret);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t sel_read_bool(struct file *filep, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filep)->i_sb->s_fs_info;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *page = NULL;
|
|
|
|
ssize_t length;
|
|
|
|
ssize_t ret;
|
|
|
|
int cur_enforcing;
|
2013-01-24 06:07:38 +08:00
|
|
|
unsigned index = file_inode(filep)->i_ino & SEL_INO_MASK;
|
2007-11-27 00:12:53 +08:00
|
|
|
const char *name = filep->f_path.dentry->d_name.name;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_lock(&fsi->state->policy_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
ret = -EINVAL;
|
2018-03-20 23:59:11 +08:00
|
|
|
if (index >= fsi->bool_num || strcmp(name,
|
|
|
|
fsi->bool_pending_names[index]))
|
2018-06-29 08:39:54 +08:00
|
|
|
goto out_unlock;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
ret = -ENOMEM;
|
2008-04-18 02:15:45 +08:00
|
|
|
page = (char *)get_zeroed_page(GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!page)
|
2018-06-29 08:39:54 +08:00
|
|
|
goto out_unlock;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
cur_enforcing = security_get_bool_value(fsi->state, index);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (cur_enforcing < 0) {
|
|
|
|
ret = cur_enforcing;
|
2018-06-29 08:39:54 +08:00
|
|
|
goto out_unlock;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
length = scnprintf(page, PAGE_SIZE, "%d %d", cur_enforcing,
|
2018-03-20 23:59:11 +08:00
|
|
|
fsi->bool_pending_values[index]);
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_unlock(&fsi->state->policy_mutex);
|
2018-06-29 08:39:54 +08:00
|
|
|
ret = simple_read_from_buffer(buf, count, ppos, page, length);
|
|
|
|
out_free:
|
2010-11-24 00:40:08 +08:00
|
|
|
free_page((unsigned long)page);
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
2018-06-29 08:39:54 +08:00
|
|
|
|
|
|
|
out_unlock:
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_unlock(&fsi->state->policy_mutex);
|
2018-06-29 08:39:54 +08:00
|
|
|
goto out_free;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t sel_write_bool(struct file *filep, const char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filep)->i_sb->s_fs_info;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *page = NULL;
|
2007-11-27 00:12:53 +08:00
|
|
|
ssize_t length;
|
2005-04-17 06:20:36 +08:00
|
|
|
int new_value;
|
2013-01-24 06:07:38 +08:00
|
|
|
unsigned index = file_inode(filep)->i_ino & SEL_INO_MASK;
|
2007-11-27 00:12:53 +08:00
|
|
|
const char *name = filep->f_path.dentry->d_name.name;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-06-29 08:39:54 +08:00
|
|
|
if (count >= PAGE_SIZE)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* No partial writes. */
|
|
|
|
if (*ppos != 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
page = memdup_user_nul(buf, count);
|
|
|
|
if (IS_ERR(page))
|
|
|
|
return PTR_ERR(page);
|
|
|
|
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_lock(&fsi->state->policy_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__SETBOOL,
|
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = -EINVAL;
|
2018-03-20 23:59:11 +08:00
|
|
|
if (index >= fsi->bool_num || strcmp(name,
|
|
|
|
fsi->bool_pending_names[index]))
|
2007-11-27 00:12:53 +08:00
|
|
|
goto out;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
length = -EINVAL;
|
|
|
|
if (sscanf(page, "%d", &new_value) != 1)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (new_value)
|
|
|
|
new_value = 1;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
fsi->bool_pending_values[index] = new_value;
|
2005-04-17 06:20:36 +08:00
|
|
|
length = count;
|
|
|
|
|
|
|
|
out:
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_unlock(&fsi->state->policy_mutex);
|
2015-12-24 13:08:06 +08:00
|
|
|
kfree(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_bool_ops = {
|
2008-04-18 02:15:45 +08:00
|
|
|
.read = sel_read_bool,
|
|
|
|
.write = sel_write_bool,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static ssize_t sel_commit_bools_write(struct file *filep,
|
|
|
|
const char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filep)->i_sb->s_fs_info;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *page = NULL;
|
2007-11-27 00:12:53 +08:00
|
|
|
ssize_t length;
|
2005-04-17 06:20:36 +08:00
|
|
|
int new_value;
|
|
|
|
|
2018-06-29 08:39:54 +08:00
|
|
|
if (count >= PAGE_SIZE)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
/* No partial writes. */
|
|
|
|
if (*ppos != 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
page = memdup_user_nul(buf, count);
|
|
|
|
if (IS_ERR(page))
|
|
|
|
return PTR_ERR(page);
|
|
|
|
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_lock(&fsi->state->policy_mutex);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__SETBOOL,
|
|
|
|
NULL);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (length)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
length = -EINVAL;
|
|
|
|
if (sscanf(page, "%d", &new_value) != 1)
|
|
|
|
goto out;
|
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
length = 0;
|
2018-03-20 23:59:11 +08:00
|
|
|
if (new_value && fsi->bool_pending_values)
|
|
|
|
length = security_set_bools(fsi->state, fsi->bool_num,
|
|
|
|
fsi->bool_pending_values);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!length)
|
|
|
|
length = count;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
out:
|
2020-08-27 01:28:53 +08:00
|
|
|
mutex_unlock(&fsi->state->policy_mutex);
|
2015-12-24 13:08:06 +08:00
|
|
|
kfree(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_commit_bools_ops = {
|
2008-04-18 02:15:45 +08:00
|
|
|
.write = sel_commit_bools_write,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2007-05-23 21:12:07 +08:00
|
|
|
static void sel_remove_entries(struct dentry *de)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2014-12-25 03:56:48 +08:00
|
|
|
d_genocide(de);
|
|
|
|
shrink_dcache_parent(de);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2020-08-20 03:59:33 +08:00
|
|
|
static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_dir,
|
|
|
|
unsigned int *bool_num, char ***bool_pending_names,
|
|
|
|
unsigned int **bool_pending_values)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2020-02-03 19:27:20 +08:00
|
|
|
int ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
ssize_t len;
|
|
|
|
struct dentry *dentry = NULL;
|
|
|
|
struct inode *inode = NULL;
|
|
|
|
struct inode_security_struct *isec;
|
|
|
|
char **names = NULL, *page;
|
2020-02-03 19:27:20 +08:00
|
|
|
u32 i, num;
|
2005-04-17 06:20:36 +08:00
|
|
|
int *values = NULL;
|
|
|
|
u32 sid;
|
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
ret = -ENOMEM;
|
2008-04-18 02:15:45 +08:00
|
|
|
page = (char *)get_zeroed_page(GFP_KERNEL);
|
|
|
|
if (!page)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
ret = security_get_bools(newpolicy, &num, &names, &values);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (ret)
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
|
|
|
|
|
|
|
for (i = 0; i < num; i++) {
|
2010-11-24 00:40:08 +08:00
|
|
|
ret = -ENOMEM;
|
2020-08-20 03:59:33 +08:00
|
|
|
dentry = d_alloc_name(bool_dir, names[i]);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!dentry)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
ret = -ENOMEM;
|
2020-08-20 03:59:33 +08:00
|
|
|
inode = sel_make_inode(bool_dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR);
|
2018-08-05 17:10:36 +08:00
|
|
|
if (!inode) {
|
|
|
|
dput(dentry);
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2018-08-05 17:10:36 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
ret = -ENAMETOOLONG;
|
2012-04-03 07:40:47 +08:00
|
|
|
len = snprintf(page, PAGE_SIZE, "/%s/%s", BOOL_DIR_NAME, names[i]);
|
2018-08-05 17:10:36 +08:00
|
|
|
if (len >= PAGE_SIZE) {
|
|
|
|
dput(dentry);
|
|
|
|
iput(inode);
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2018-08-05 17:10:36 +08:00
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2018-09-22 08:19:11 +08:00
|
|
|
isec = selinux_inode(inode);
|
2020-08-07 21:29:34 +08:00
|
|
|
ret = selinux_policy_genfs_sid(newpolicy, "selinuxfs", page,
|
2018-03-02 07:48:02 +08:00
|
|
|
SECCLASS_FILE, &sid);
|
2017-01-09 23:07:31 +08:00
|
|
|
if (ret) {
|
2017-01-09 23:07:32 +08:00
|
|
|
pr_warn_ratelimited("SELinux: no sid found, defaulting to security isid for %s\n",
|
|
|
|
page);
|
|
|
|
sid = SECINITSID_SECURITY;
|
2017-01-09 23:07:31 +08:00
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
isec->sid = sid;
|
2016-11-11 05:18:27 +08:00
|
|
|
isec->initialized = LABEL_INITIALIZED;
|
2005-04-17 06:20:36 +08:00
|
|
|
inode->i_fop = &sel_bool_ops;
|
2007-04-05 04:18:50 +08:00
|
|
|
inode->i_ino = i|SEL_BOOL_INO_OFFSET;
|
2005-04-17 06:20:36 +08:00
|
|
|
d_add(dentry, inode);
|
|
|
|
}
|
2020-08-20 03:59:33 +08:00
|
|
|
*bool_num = num;
|
|
|
|
*bool_pending_names = names;
|
|
|
|
*bool_pending_values = values;
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
free_page((unsigned long)page);
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
out:
|
|
|
|
free_page((unsigned long)page);
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
if (names) {
|
2005-06-26 05:58:51 +08:00
|
|
|
for (i = 0; i < num; i++)
|
|
|
|
kfree(names[i]);
|
2005-04-17 06:20:36 +08:00
|
|
|
kfree(names);
|
|
|
|
}
|
2005-10-24 03:57:16 +08:00
|
|
|
kfree(values);
|
2020-08-20 03:59:33 +08:00
|
|
|
sel_remove_entries(bool_dir);
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t sel_read_avc_cache_threshold(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-06 00:47:56 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2005-04-17 06:20:36 +08:00
|
|
|
char tmpbuf[TMPBUFLEN];
|
|
|
|
ssize_t length;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = scnprintf(tmpbuf, TMPBUFLEN, "%u",
|
|
|
|
avc_get_cache_threshold(state->avc));
|
2005-04-17 06:20:36 +08:00
|
|
|
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
|
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_write_avc_cache_threshold(struct file *file,
|
|
|
|
const char __user *buf,
|
2005-04-17 06:20:36 +08:00
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
|
|
|
|
{
|
2018-03-06 00:47:56 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2015-12-24 13:08:06 +08:00
|
|
|
char *page;
|
2005-04-17 06:20:36 +08:00
|
|
|
ssize_t ret;
|
2016-06-11 05:14:26 +08:00
|
|
|
unsigned int new_value;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
ret = avc_has_perm(&selinux_state,
|
|
|
|
current_sid(), SECINITSID_SECURITY,
|
2017-01-09 23:07:31 +08:00
|
|
|
SECCLASS_SECURITY, SECURITY__SETSECPARAM,
|
|
|
|
NULL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (ret)
|
2015-12-24 13:08:06 +08:00
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
if (count >= PAGE_SIZE)
|
2015-12-24 13:08:06 +08:00
|
|
|
return -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
/* No partial writes. */
|
|
|
|
if (*ppos != 0)
|
2015-12-24 13:08:06 +08:00
|
|
|
return -EINVAL;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2015-12-24 13:08:06 +08:00
|
|
|
page = memdup_user_nul(buf, count);
|
|
|
|
if (IS_ERR(page))
|
|
|
|
return PTR_ERR(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
ret = -EINVAL;
|
|
|
|
if (sscanf(page, "%u", &new_value) != 1)
|
2005-04-17 06:20:36 +08:00
|
|
|
goto out;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
avc_set_cache_threshold(state->avc, new_value);
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
ret = count;
|
|
|
|
out:
|
2015-12-24 13:08:06 +08:00
|
|
|
kfree(page);
|
2005-04-17 06:20:36 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t sel_read_avc_hash_stats(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-06 00:47:56 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
2005-04-17 06:20:36 +08:00
|
|
|
char *page;
|
2010-11-24 00:40:08 +08:00
|
|
|
ssize_t length;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
page = (char *)__get_free_page(GFP_KERNEL);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!page)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2018-03-06 00:47:56 +08:00
|
|
|
length = avc_get_hash_stats(state->avc, page);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (length >= 0)
|
|
|
|
length = simple_read_from_buffer(buf, count, ppos, page, length);
|
2005-04-17 06:20:36 +08:00
|
|
|
free_page((unsigned long)page);
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
return length;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2019-11-22 17:33:06 +08:00
|
|
|
static ssize_t sel_read_sidtab_hash_stats(struct file *filp, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
|
|
|
struct selinux_fs_info *fsi = file_inode(filp)->i_sb->s_fs_info;
|
|
|
|
struct selinux_state *state = fsi->state;
|
|
|
|
char *page;
|
|
|
|
ssize_t length;
|
|
|
|
|
|
|
|
page = (char *)__get_free_page(GFP_KERNEL);
|
|
|
|
if (!page)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
length = security_sidtab_hash_stats(state, page);
|
|
|
|
if (length >= 0)
|
|
|
|
length = simple_read_from_buffer(buf, count, ppos, page,
|
|
|
|
length);
|
|
|
|
free_page((unsigned long)page);
|
|
|
|
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations sel_sidtab_hash_stats_ops = {
|
|
|
|
.read = sel_read_sidtab_hash_stats,
|
|
|
|
.llseek = generic_file_llseek,
|
|
|
|
};
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_avc_cache_threshold_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.read = sel_read_avc_cache_threshold,
|
|
|
|
.write = sel_write_avc_cache_threshold,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_avc_hash_stats_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.read = sel_read_avc_hash_stats,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
|
|
|
|
static struct avc_cache_stats *sel_avc_get_stat_idx(loff_t *idx)
|
|
|
|
{
|
|
|
|
int cpu;
|
|
|
|
|
2009-01-01 07:42:15 +08:00
|
|
|
for (cpu = *idx; cpu < nr_cpu_ids; ++cpu) {
|
2005-04-17 06:20:36 +08:00
|
|
|
if (!cpu_possible(cpu))
|
|
|
|
continue;
|
|
|
|
*idx = cpu + 1;
|
|
|
|
return &per_cpu(avc_cache_stats, cpu);
|
|
|
|
}
|
selinux: sel_avc_get_stat_idx should increase position index
If seq_file .next function does not change position index,
read after some lseek can generate unexpected output.
$ dd if=/sys/fs/selinux/avc/cache_stats # usual output
lookups hits misses allocations reclaims frees
817223 810034 7189 7189 6992 7037
1934894 1926896 7998 7998 7632 7683
1322812 1317176 5636 5636 5456 5507
1560571 1551548 9023 9023 9056 9115
0+1 records in
0+1 records out
189 bytes copied, 5,1564e-05 s, 3,7 MB/s
$# read after lseek to midle of last line
$ dd if=/sys/fs/selinux/avc/cache_stats bs=180 skip=1
dd: /sys/fs/selinux/avc/cache_stats: cannot skip to specified offset
056 9115 <<<< end of last line
1560571 1551548 9023 9023 9056 9115 <<< whole last line once again
0+1 records in
0+1 records out
45 bytes copied, 8,7221e-05 s, 516 kB/s
$# read after lseek beyond end of of file
$ dd if=/sys/fs/selinux/avc/cache_stats bs=1000 skip=1
dd: /sys/fs/selinux/avc/cache_stats: cannot skip to specified offset
1560571 1551548 9023 9023 9056 9115 <<<< generates whole last line
0+1 records in
0+1 records out
36 bytes copied, 9,0934e-05 s, 396 kB/s
https://bugzilla.kernel.org/show_bug.cgi?id=206283
Signed-off-by: Vasily Averin <vvs@virtuozzo.com>
Acked-by: Stephen Smalley <sds@tycho.nsa.gov>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-02-01 15:47:47 +08:00
|
|
|
(*idx)++;
|
2005-04-17 06:20:36 +08:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *sel_avc_stats_seq_start(struct seq_file *seq, loff_t *pos)
|
|
|
|
{
|
|
|
|
loff_t n = *pos - 1;
|
|
|
|
|
|
|
|
if (*pos == 0)
|
|
|
|
return SEQ_START_TOKEN;
|
|
|
|
|
|
|
|
return sel_avc_get_stat_idx(&n);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *sel_avc_stats_seq_next(struct seq_file *seq, void *v, loff_t *pos)
|
|
|
|
{
|
|
|
|
return sel_avc_get_stat_idx(pos);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int sel_avc_stats_seq_show(struct seq_file *seq, void *v)
|
|
|
|
{
|
|
|
|
struct avc_cache_stats *st = v;
|
|
|
|
|
2017-01-15 21:04:53 +08:00
|
|
|
if (v == SEQ_START_TOKEN) {
|
|
|
|
seq_puts(seq,
|
|
|
|
"lookups hits misses allocations reclaims frees\n");
|
|
|
|
} else {
|
2011-05-20 12:22:53 +08:00
|
|
|
unsigned int lookups = st->lookups;
|
|
|
|
unsigned int misses = st->misses;
|
|
|
|
unsigned int hits = lookups - misses;
|
|
|
|
seq_printf(seq, "%u %u %u %u %u %u\n", lookups,
|
|
|
|
hits, misses, st->allocations,
|
2005-04-17 06:20:36 +08:00
|
|
|
st->reclaims, st->frees);
|
2011-05-20 12:22:53 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void sel_avc_stats_seq_stop(struct seq_file *seq, void *v)
|
|
|
|
{ }
|
|
|
|
|
2008-01-23 07:02:58 +08:00
|
|
|
static const struct seq_operations sel_avc_cache_stats_seq_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.start = sel_avc_stats_seq_start,
|
|
|
|
.next = sel_avc_stats_seq_next,
|
|
|
|
.show = sel_avc_stats_seq_show,
|
|
|
|
.stop = sel_avc_stats_seq_stop,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int sel_open_avc_cache_stats(struct inode *inode, struct file *file)
|
|
|
|
{
|
|
|
|
return seq_open(file, &sel_avc_cache_stats_seq_ops);
|
|
|
|
}
|
|
|
|
|
2007-02-12 16:55:37 +08:00
|
|
|
static const struct file_operations sel_avc_cache_stats_ops = {
|
2005-04-17 06:20:36 +08:00
|
|
|
.open = sel_open_avc_cache_stats,
|
|
|
|
.read = seq_read,
|
|
|
|
.llseek = seq_lseek,
|
|
|
|
.release = seq_release,
|
|
|
|
};
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static int sel_make_avc_files(struct dentry *dir)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct super_block *sb = dir->d_sb;
|
|
|
|
struct selinux_fs_info *fsi = sb->s_fs_info;
|
2010-11-24 00:40:08 +08:00
|
|
|
int i;
|
2017-03-26 12:15:37 +08:00
|
|
|
static const struct tree_descr files[] = {
|
2005-04-17 06:20:36 +08:00
|
|
|
{ "cache_threshold",
|
|
|
|
&sel_avc_cache_threshold_ops, S_IRUGO|S_IWUSR },
|
|
|
|
{ "hash_stats", &sel_avc_hash_stats_ops, S_IRUGO },
|
|
|
|
#ifdef CONFIG_SECURITY_SELINUX_AVC_STATS
|
|
|
|
{ "cache_stats", &sel_avc_cache_stats_ops, S_IRUGO },
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
2006-01-06 16:11:22 +08:00
|
|
|
for (i = 0; i < ARRAY_SIZE(files); i++) {
|
2005-04-17 06:20:36 +08:00
|
|
|
struct inode *inode;
|
|
|
|
struct dentry *dentry;
|
|
|
|
|
|
|
|
dentry = d_alloc_name(dir, files[i].name);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!dentry)
|
|
|
|
return -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode);
|
2018-08-05 17:10:36 +08:00
|
|
|
if (!inode) {
|
|
|
|
dput(dentry);
|
2010-11-24 00:40:08 +08:00
|
|
|
return -ENOMEM;
|
2018-08-05 17:10:36 +08:00
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
inode->i_fop = files[i].ops;
|
2018-03-20 23:59:11 +08:00
|
|
|
inode->i_ino = ++fsi->last_ino;
|
2005-04-17 06:20:36 +08:00
|
|
|
d_add(dentry, inode);
|
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2019-11-22 17:33:06 +08:00
|
|
|
static int sel_make_ss_files(struct dentry *dir)
|
|
|
|
{
|
|
|
|
struct super_block *sb = dir->d_sb;
|
|
|
|
struct selinux_fs_info *fsi = sb->s_fs_info;
|
|
|
|
int i;
|
|
|
|
static struct tree_descr files[] = {
|
|
|
|
{ "sidtab_hash_stats", &sel_sidtab_hash_stats_ops, S_IRUGO },
|
|
|
|
};
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(files); i++) {
|
|
|
|
struct inode *inode;
|
|
|
|
struct dentry *dentry;
|
|
|
|
|
|
|
|
dentry = d_alloc_name(dir, files[i].name);
|
|
|
|
if (!dentry)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
|
|
|
inode = sel_make_inode(dir->d_sb, S_IFREG|files[i].mode);
|
|
|
|
if (!inode) {
|
|
|
|
dput(dentry);
|
|
|
|
return -ENOMEM;
|
|
|
|
}
|
|
|
|
|
|
|
|
inode->i_fop = files[i].ops;
|
|
|
|
inode->i_ino = ++fsi->last_ino;
|
|
|
|
d_add(dentry, inode);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_read_initcon(struct file *file, char __user *buf,
|
2007-04-04 22:11:29 +08:00
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
2007-04-04 22:11:29 +08:00
|
|
|
char *con;
|
|
|
|
u32 sid, len;
|
|
|
|
ssize_t ret;
|
|
|
|
|
2013-01-24 06:07:38 +08:00
|
|
|
sid = file_inode(file)->i_ino&SEL_INO_MASK;
|
2018-03-20 23:59:11 +08:00
|
|
|
ret = security_sid_to_context(fsi->state, sid, &con, &len);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (ret)
|
2007-04-04 22:11:29 +08:00
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = simple_read_from_buffer(buf, count, ppos, con, len);
|
|
|
|
kfree(con);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations sel_initcon_ops = {
|
|
|
|
.read = sel_read_initcon,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2007-04-04 22:11:29 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static int sel_make_initcon_files(struct dentry *dir)
|
|
|
|
{
|
2010-11-24 00:40:08 +08:00
|
|
|
int i;
|
2007-04-04 22:11:29 +08:00
|
|
|
|
|
|
|
for (i = 1; i <= SECINITSID_NUM; i++) {
|
|
|
|
struct inode *inode;
|
|
|
|
struct dentry *dentry;
|
selinux: remove unused initial SIDs and improve handling
Remove initial SIDs that have never been used or are no longer used by
the kernel from its string table, which is also used to generate the
SECINITSID_* symbols referenced in code. Update the code to
gracefully handle the fact that these can now be NULL. Stop treating
it as an error if a policy defines additional initial SIDs unknown to
the kernel. Do not load unused initial SID contexts into the sidtab.
Fix the incorrect usage of the name from the ocontext in error
messages when loading initial SIDs since these are not presently
written to the kernel policy and are therefore always NULL.
After this change, it is possible to safely reclaim and reuse some of
the unused initial SIDs without compatibility issues. Specifically,
unused initial SIDs that were being assigned the same context as the
unlabeled initial SID in policies can be reclaimed and reused for
another purpose, with existing policies still treating them as having
the unlabeled context and future policies having the option of mapping
them to a more specific context. For example, this could have been
used when the infiniband labeling support was introduced to define
initial SIDs for the default pkey and endport SIDs similar to the
handling of port/netif/node SIDs rather than always using
SECINITSID_UNLABELED as the default.
The set of safely reclaimable unused initial SIDs across all known
policies is igmp_packet (13), icmp_socket (14), tcp_socket (15), kmod
(24), policy (25), and scmp_packet (26); these initial SIDs were
assigned the same context as unlabeled in all known policies including
mls. If only considering non-mls policies (i.e. assuming that mls
users always upgrade policy with their kernels), the set of safely
reclaimable unused initial SIDs further includes file_labels (6), init
(7), sysctl_modprobe (16), and sysctl_fs (18) through sysctl_dev (23).
Adding new initial SIDs beyond SECINITSID_NUM to policy unfortunately
became a fatal error in commit 24ed7fdae669 ("selinux: use separate
table for initial SID lookup") and even before that it could cause
problems on a policy reload (collision between the new initial SID and
one allocated at runtime) ever since commit 42596eafdd75 ("selinux:
load the initial SIDs upon every policy load") so we cannot safely
start adding new initial SIDs to policies beyond SECINITSID_NUM (27)
until such a time as all such kernels do not need to be supported and
only those that include this commit are relevant. That is not a big
deal since we haven't added a new initial SID since 2004 (v2.6.7) and
we have plenty of unused ones we can reclaim if we truly need one.
If we want to avoid the wasted storage in initial_sid_to_string[]
and/or sidtab->isids[] for the unused initial SIDs, we could introduce
an indirection between the kernel initial SID values and the policy
initial SID values and just map the policy SID values in the ocontexts
to the kernel values during policy_load_isids(). Originally I thought
we'd do this by preserving the initial SID names in the kernel policy
and creating a mapping at load time like we do for the security
classes and permissions but that would require a new kernel policy
format version and associated changes to libsepol/checkpolicy and I'm
not sure it is justified. Simpler approach is just to create a fixed
mapping table in the kernel from the existing fixed policy values to
the kernel values. Less flexible but probably sufficient.
A separate selinux userspace change was applied in
https://github.com/SELinuxProject/selinux/commit/8677ce5e8f592950ae6f14cea1b68a20ddc1ac25
to enable removal of most of the unused initial SID contexts from
policies, but there is no dependency between that change and this one.
That change permits removing all of the unused initial SID contexts
from policy except for the fs and sysctl SID contexts. The initial
SID declarations themselves would remain in policy to preserve the
values of subsequent ones but the contexts can be dropped. If/when
the kernel decides to reuse one of them, future policies can change
the name and start assigning a context again without breaking
compatibility.
Here is how I would envision staging changes to the initial SIDs in a
compatible manner after this commit is applied:
1. At any time after this commit is applied, the kernel could choose
to reclaim one of the safely reclaimable unused initial SIDs listed
above for a new purpose (i.e. replace its NULL entry in the
initial_sid_to_string[] table with a new name and start using the
newly generated SECINITSID_name symbol in code), and refpolicy could
at that time rename its declaration of that initial SID to reflect its
new purpose and start assigning it a context going
forward. Existing/old policies would map the reclaimed initial SID to
the unlabeled context, so that would be the initial default behavior
until policies are updated. This doesn't depend on the selinux
userspace change; it will work with existing policies and userspace.
2. In 6 months or so we'll have another SELinux userspace release that
will include the libsepol/checkpolicy support for omitting unused
initial SID contexts.
3. At any time after that release, refpolicy can make that release its
minimum build requirement and drop the sid context statements (but not
the sid declarations) for all of the unused initial SIDs except for
fs and sysctl, which must remain for compatibility on policy
reload with old kernels and for compatibility with kernels that were
still using SECINITSID_SYSCTL (< 2.6.39). This doesn't depend on this
kernel commit; it will work with previous kernels as well.
4. After N years for some value of N, refpolicy decides that it no
longer cares about policy reload compatibility for kernels that
predate this kernel commit, and refpolicy drops the fs and sysctl
SID contexts from policy too (but retains the declarations).
5. After M years for some value of M, the kernel decides that it no
longer cares about compatibility with refpolicies that predate step 4
(dropping the fs and sysctl SIDs), and those two SIDs also become
safely reclaimable. This step is optional and need not ever occur unless
we decide that the need to reclaim those two SIDs outweighs the
compatibility cost.
6. After O years for some value of O, refpolicy decides that it no
longer cares about policy load (not just reload) compatibility for
kernels that predate this kernel commit, and both kernel and refpolicy
can then start adding and using new initial SIDs beyond 27. This does
not depend on the previous change (step 5) and can occur independent
of it.
Fixes: https://github.com/SELinuxProject/selinux-kernel/issues/12
Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-02-25 00:10:23 +08:00
|
|
|
const char *s = security_get_initial_sid_context(i);
|
|
|
|
|
|
|
|
if (!s)
|
|
|
|
continue;
|
|
|
|
dentry = d_alloc_name(dir, s);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!dentry)
|
|
|
|
return -ENOMEM;
|
2007-04-04 22:11:29 +08:00
|
|
|
|
|
|
|
inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO);
|
2018-08-05 17:10:36 +08:00
|
|
|
if (!inode) {
|
|
|
|
dput(dentry);
|
2010-11-24 00:40:08 +08:00
|
|
|
return -ENOMEM;
|
2018-08-05 17:10:36 +08:00
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2007-04-04 22:11:29 +08:00
|
|
|
inode->i_fop = &sel_initcon_ops;
|
|
|
|
inode->i_ino = i|SEL_INITCON_INO_OFFSET;
|
|
|
|
d_add(dentry, inode);
|
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
|
|
|
return 0;
|
2007-04-04 22:11:29 +08:00
|
|
|
}
|
|
|
|
|
2007-05-23 21:12:09 +08:00
|
|
|
static inline unsigned long sel_class_to_ino(u16 class)
|
|
|
|
{
|
|
|
|
return (class * (SEL_VEC_MAX + 1)) | SEL_CLASS_INO_OFFSET;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u16 sel_ino_to_class(unsigned long ino)
|
|
|
|
{
|
2012-04-05 01:46:46 +08:00
|
|
|
return (ino & SEL_INO_MASK) / (SEL_VEC_MAX + 1);
|
2007-05-23 21:12:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline unsigned long sel_perm_to_ino(u16 class, u32 perm)
|
|
|
|
{
|
|
|
|
return (class * (SEL_VEC_MAX + 1) + perm) | SEL_CLASS_INO_OFFSET;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline u32 sel_ino_to_perm(unsigned long ino)
|
|
|
|
{
|
|
|
|
return (ino & SEL_INO_MASK) % (SEL_VEC_MAX + 1);
|
|
|
|
}
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_read_class(struct file *file, char __user *buf,
|
2007-05-23 21:12:09 +08:00
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2013-01-24 06:07:38 +08:00
|
|
|
unsigned long ino = file_inode(file)->i_ino;
|
2012-04-03 07:40:47 +08:00
|
|
|
char res[TMPBUFLEN];
|
2020-01-07 09:39:18 +08:00
|
|
|
ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_class(ino));
|
2012-04-03 07:40:47 +08:00
|
|
|
return simple_read_from_buffer(buf, count, ppos, res, len);
|
2007-05-23 21:12:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations sel_class_ops = {
|
|
|
|
.read = sel_read_class,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2007-05-23 21:12:09 +08:00
|
|
|
};
|
|
|
|
|
2008-04-18 02:15:45 +08:00
|
|
|
static ssize_t sel_read_perm(struct file *file, char __user *buf,
|
2007-05-23 21:12:09 +08:00
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2013-01-24 06:07:38 +08:00
|
|
|
unsigned long ino = file_inode(file)->i_ino;
|
2012-04-03 07:40:47 +08:00
|
|
|
char res[TMPBUFLEN];
|
2020-01-07 09:39:18 +08:00
|
|
|
ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_perm(ino));
|
2012-04-03 07:40:47 +08:00
|
|
|
return simple_read_from_buffer(buf, count, ppos, res, len);
|
2007-05-23 21:12:09 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations sel_perm_ops = {
|
|
|
|
.read = sel_read_perm,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2007-05-23 21:12:09 +08:00
|
|
|
};
|
|
|
|
|
2008-01-29 21:38:19 +08:00
|
|
|
static ssize_t sel_read_policycap(struct file *file, char __user *buf,
|
|
|
|
size_t count, loff_t *ppos)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
|
2008-01-29 21:38:19 +08:00
|
|
|
int value;
|
|
|
|
char tmpbuf[TMPBUFLEN];
|
|
|
|
ssize_t length;
|
2013-01-24 06:07:38 +08:00
|
|
|
unsigned long i_ino = file_inode(file)->i_ino;
|
2008-01-29 21:38:19 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
value = security_policycap_supported(fsi->state, i_ino & SEL_INO_MASK);
|
2008-01-29 21:38:19 +08:00
|
|
|
length = scnprintf(tmpbuf, TMPBUFLEN, "%d", value);
|
|
|
|
|
|
|
|
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct file_operations sel_policycap_ops = {
|
|
|
|
.read = sel_read_policycap,
|
2010-07-08 05:40:10 +08:00
|
|
|
.llseek = generic_file_llseek,
|
2008-01-29 21:38:19 +08:00
|
|
|
};
|
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
static int sel_make_perm_files(struct selinux_policy *newpolicy,
|
|
|
|
char *objclass, int classvalue,
|
|
|
|
struct dentry *dir)
|
2007-05-23 21:12:09 +08:00
|
|
|
{
|
2010-11-24 00:40:08 +08:00
|
|
|
int i, rc, nperms;
|
2007-05-23 21:12:09 +08:00
|
|
|
char **perms;
|
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
rc = security_get_permissions(newpolicy, objclass, &perms, &nperms);
|
2007-05-23 21:12:09 +08:00
|
|
|
if (rc)
|
2010-11-24 00:40:08 +08:00
|
|
|
return rc;
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
for (i = 0; i < nperms; i++) {
|
|
|
|
struct inode *inode;
|
|
|
|
struct dentry *dentry;
|
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
rc = -ENOMEM;
|
2007-05-23 21:12:09 +08:00
|
|
|
dentry = d_alloc_name(dir, perms[i]);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!dentry)
|
|
|
|
goto out;
|
2007-05-23 21:12:09 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
rc = -ENOMEM;
|
2007-05-23 21:12:09 +08:00
|
|
|
inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO);
|
2018-08-05 17:10:36 +08:00
|
|
|
if (!inode) {
|
|
|
|
dput(dentry);
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2018-08-05 17:10:36 +08:00
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2007-05-23 21:12:09 +08:00
|
|
|
inode->i_fop = &sel_perm_ops;
|
|
|
|
/* i+1 since perm values are 1-indexed */
|
2010-04-09 19:30:29 +08:00
|
|
|
inode->i_ino = sel_perm_to_ino(classvalue, i + 1);
|
2007-05-23 21:12:09 +08:00
|
|
|
d_add(dentry, inode);
|
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
rc = 0;
|
|
|
|
out:
|
2007-05-23 21:12:09 +08:00
|
|
|
for (i = 0; i < nperms; i++)
|
|
|
|
kfree(perms[i]);
|
|
|
|
kfree(perms);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
static int sel_make_class_dir_entries(struct selinux_policy *newpolicy,
|
|
|
|
char *classname, int index,
|
|
|
|
struct dentry *dir)
|
2007-05-23 21:12:09 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct super_block *sb = dir->d_sb;
|
|
|
|
struct selinux_fs_info *fsi = sb->s_fs_info;
|
2007-05-23 21:12:09 +08:00
|
|
|
struct dentry *dentry = NULL;
|
|
|
|
struct inode *inode = NULL;
|
|
|
|
int rc;
|
|
|
|
|
|
|
|
dentry = d_alloc_name(dir, "index");
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!dentry)
|
|
|
|
return -ENOMEM;
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
inode = sel_make_inode(dir->d_sb, S_IFREG|S_IRUGO);
|
2018-08-05 17:10:36 +08:00
|
|
|
if (!inode) {
|
|
|
|
dput(dentry);
|
2010-11-24 00:40:08 +08:00
|
|
|
return -ENOMEM;
|
2018-08-05 17:10:36 +08:00
|
|
|
}
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
inode->i_fop = &sel_class_ops;
|
|
|
|
inode->i_ino = sel_class_to_ino(index);
|
|
|
|
d_add(dentry, inode);
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
dentry = sel_make_dir(dir, "perms", &fsi->last_class_ino);
|
2012-03-19 08:36:59 +08:00
|
|
|
if (IS_ERR(dentry))
|
|
|
|
return PTR_ERR(dentry);
|
2007-05-23 21:12:09 +08:00
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
rc = sel_make_perm_files(newpolicy, classname, index, dentry);
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2020-08-20 03:59:33 +08:00
|
|
|
static int sel_make_classes(struct selinux_policy *newpolicy,
|
|
|
|
struct dentry *class_dir,
|
|
|
|
unsigned long *last_class_ino)
|
2007-05-23 21:12:09 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
int rc, nclasses, i;
|
2007-05-23 21:12:09 +08:00
|
|
|
char **classes;
|
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
rc = security_get_classes(newpolicy, &classes, &nclasses);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (rc)
|
|
|
|
return rc;
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
/* +2 since classes are 1-indexed */
|
2020-08-20 03:59:33 +08:00
|
|
|
*last_class_ino = sel_class_to_ino(nclasses + 2);
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
for (i = 0; i < nclasses; i++) {
|
|
|
|
struct dentry *class_name_dir;
|
|
|
|
|
2020-08-20 03:59:33 +08:00
|
|
|
class_name_dir = sel_make_dir(class_dir, classes[i],
|
|
|
|
last_class_ino);
|
2012-03-19 08:36:59 +08:00
|
|
|
if (IS_ERR(class_name_dir)) {
|
|
|
|
rc = PTR_ERR(class_name_dir);
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2012-03-19 08:36:59 +08:00
|
|
|
}
|
2007-05-23 21:12:09 +08:00
|
|
|
|
|
|
|
/* i+1 since class values are 1-indexed */
|
2020-08-07 21:29:34 +08:00
|
|
|
rc = sel_make_class_dir_entries(newpolicy, classes[i], i + 1,
|
2007-05-23 21:12:09 +08:00
|
|
|
class_name_dir);
|
|
|
|
if (rc)
|
2010-11-24 00:40:08 +08:00
|
|
|
goto out;
|
2007-05-23 21:12:09 +08:00
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
rc = 0;
|
|
|
|
out:
|
2007-05-23 21:12:09 +08:00
|
|
|
for (i = 0; i < nclasses; i++)
|
|
|
|
kfree(classes[i]);
|
|
|
|
kfree(classes);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
static int sel_make_policycap(struct selinux_fs_info *fsi)
|
2008-01-29 21:38:19 +08:00
|
|
|
{
|
|
|
|
unsigned int iter;
|
|
|
|
struct dentry *dentry = NULL;
|
|
|
|
struct inode *inode = NULL;
|
|
|
|
|
|
|
|
for (iter = 0; iter <= POLICYDB_CAPABILITY_MAX; iter++) {
|
2017-05-19 04:58:31 +08:00
|
|
|
if (iter < ARRAY_SIZE(selinux_policycap_names))
|
2018-03-20 23:59:11 +08:00
|
|
|
dentry = d_alloc_name(fsi->policycap_dir,
|
2017-05-19 04:58:31 +08:00
|
|
|
selinux_policycap_names[iter]);
|
2008-01-29 21:38:19 +08:00
|
|
|
else
|
2018-03-20 23:59:11 +08:00
|
|
|
dentry = d_alloc_name(fsi->policycap_dir, "unknown");
|
2008-01-29 21:38:19 +08:00
|
|
|
|
|
|
|
if (dentry == NULL)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
inode = sel_make_inode(fsi->sb, S_IFREG | 0444);
|
2018-08-05 17:10:36 +08:00
|
|
|
if (inode == NULL) {
|
|
|
|
dput(dentry);
|
2008-01-29 21:38:19 +08:00
|
|
|
return -ENOMEM;
|
2018-08-05 17:10:36 +08:00
|
|
|
}
|
2008-01-29 21:38:19 +08:00
|
|
|
|
|
|
|
inode->i_fop = &sel_policycap_ops;
|
|
|
|
inode->i_ino = iter | SEL_POLICYCAP_INO_OFFSET;
|
|
|
|
d_add(dentry, inode);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-03-19 08:36:59 +08:00
|
|
|
static struct dentry *sel_make_dir(struct dentry *dir, const char *name,
|
2007-05-23 21:12:08 +08:00
|
|
|
unsigned long *ino)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2012-03-19 08:36:59 +08:00
|
|
|
struct dentry *dentry = d_alloc_name(dir, name);
|
2005-04-17 06:20:36 +08:00
|
|
|
struct inode *inode;
|
|
|
|
|
2012-03-19 08:36:59 +08:00
|
|
|
if (!dentry)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
inode = sel_make_inode(dir->d_sb, S_IFDIR | S_IRUGO | S_IXUGO);
|
|
|
|
if (!inode) {
|
|
|
|
dput(dentry);
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
inode->i_op = &simple_dir_inode_operations;
|
|
|
|
inode->i_fop = &simple_dir_operations;
|
2007-05-23 21:12:08 +08:00
|
|
|
inode->i_ino = ++(*ino);
|
2006-03-22 16:09:16 +08:00
|
|
|
/* directory inodes start off with i_nlink == 2 (for "." entry) */
|
2006-10-01 14:29:04 +08:00
|
|
|
inc_nlink(inode);
|
2005-04-17 06:20:36 +08:00
|
|
|
d_add(dentry, inode);
|
2006-03-22 16:09:20 +08:00
|
|
|
/* bump link count on parent directory, too */
|
2015-02-19 18:47:02 +08:00
|
|
|
inc_nlink(d_inode(dir));
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2012-03-19 08:36:59 +08:00
|
|
|
return dentry;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
selinux: Create new booleans and class dirs out of tree
In order to avoid concurrency issues around selinuxfs resource availability
during policy load, we first create new directories out of tree for
reloaded resources, then swap them in, and finally delete the old versions.
This fix focuses on concurrency in each of the two subtrees swapped, and
not concurrency between the trees. This means that it is still possible
that subsequent reads to eg the booleans directory and the class directory
during a policy load could see the old state for one and the new for the other.
The problem of ensuring that policy loads are fully atomic from the perspective
of userspace is larger than what is dealt with here. This commit focuses on
ensuring that the directories contents always match either the new or the old
policy state from the perspective of userspace.
In the previous implementation, on policy load /sys/fs/selinux is updated
by deleting the previous contents of
/sys/fs/selinux/{class,booleans} and then recreating them. This means
that there is a period of time when the contents of these directories do not
exist which can cause race conditions as userspace relies on them for
information about the policy. In addition, it means that error recovery in
the event of failure is challenging.
In order to demonstrate the race condition that this series fixes, you
can use the following commands:
while true; do cat /sys/fs/selinux/class/service/perms/status
>/dev/null; done &
while true; do load_policy; done;
In the existing code, this will display errors fairly often as the class
lookup fails. (In normal operation from systemd, this would result in a
permission check which would be allowed or denied based on policy settings
around unknown object classes.) After applying this patch series you
should expect to no longer see such error messages.
Signed-off-by: Daniel Burgener <dburgener@linux.microsoft.com>
Acked-by: Stephen Smalley <stephen.smalley.work@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
2020-08-20 03:59:35 +08:00
|
|
|
static struct dentry *sel_make_disconnected_dir(struct super_block *sb,
|
|
|
|
unsigned long *ino)
|
|
|
|
{
|
|
|
|
struct inode *inode = sel_make_inode(sb, S_IFDIR | S_IRUGO | S_IXUGO);
|
|
|
|
|
|
|
|
if (!inode)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
inode->i_op = &simple_dir_inode_operations;
|
|
|
|
inode->i_fop = &simple_dir_operations;
|
|
|
|
inode->i_ino = ++(*ino);
|
|
|
|
/* directory inodes start off with i_nlink == 2 (for "." entry) */
|
|
|
|
inc_nlink(inode);
|
|
|
|
return d_obtain_alias(inode);
|
|
|
|
}
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
#define NULL_FILE_NAME "null"
|
|
|
|
|
2019-03-26 00:38:30 +08:00
|
|
|
static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct selinux_fs_info *fsi;
|
2005-04-17 06:20:36 +08:00
|
|
|
int ret;
|
|
|
|
struct dentry *dentry;
|
2012-03-19 08:36:59 +08:00
|
|
|
struct inode *inode;
|
2005-04-17 06:20:36 +08:00
|
|
|
struct inode_security_struct *isec;
|
|
|
|
|
2017-03-26 12:15:37 +08:00
|
|
|
static const struct tree_descr selinux_files[] = {
|
2005-04-17 06:20:36 +08:00
|
|
|
[SEL_LOAD] = {"load", &sel_load_ops, S_IRUSR|S_IWUSR},
|
|
|
|
[SEL_ENFORCE] = {"enforce", &sel_enforce_ops, S_IRUGO|S_IWUSR},
|
2005-11-09 13:34:33 +08:00
|
|
|
[SEL_CONTEXT] = {"context", &transaction_ops, S_IRUGO|S_IWUGO},
|
2005-04-17 06:20:36 +08:00
|
|
|
[SEL_ACCESS] = {"access", &transaction_ops, S_IRUGO|S_IWUGO},
|
|
|
|
[SEL_CREATE] = {"create", &transaction_ops, S_IRUGO|S_IWUGO},
|
|
|
|
[SEL_RELABEL] = {"relabel", &transaction_ops, S_IRUGO|S_IWUGO},
|
|
|
|
[SEL_USER] = {"user", &transaction_ops, S_IRUGO|S_IWUGO},
|
|
|
|
[SEL_POLICYVERS] = {"policyvers", &sel_policyvers_ops, S_IRUGO},
|
|
|
|
[SEL_COMMIT_BOOLS] = {"commit_pending_bools", &sel_commit_bools_ops, S_IWUSR},
|
|
|
|
[SEL_MLS] = {"mls", &sel_mls_ops, S_IRUGO},
|
|
|
|
[SEL_DISABLE] = {"disable", &sel_disable_ops, S_IWUSR},
|
|
|
|
[SEL_MEMBER] = {"member", &transaction_ops, S_IRUGO|S_IWUGO},
|
|
|
|
[SEL_CHECKREQPROT] = {"checkreqprot", &sel_checkreqprot_ops, S_IRUGO|S_IWUSR},
|
2007-09-22 02:37:10 +08:00
|
|
|
[SEL_REJECT_UNKNOWN] = {"reject_unknown", &sel_handle_unknown_ops, S_IRUGO},
|
|
|
|
[SEL_DENY_UNKNOWN] = {"deny_unknown", &sel_handle_unknown_ops, S_IRUGO},
|
selinux: fast status update interface (/selinux/status)
This patch provides a new /selinux/status entry which allows applications
read-only mmap(2).
This region reflects selinux_kernel_status structure in kernel space.
struct selinux_kernel_status
{
u32 length; /* length of this structure */
u32 sequence; /* sequence number of seqlock logic */
u32 enforcing; /* current setting of enforcing mode */
u32 policyload; /* times of policy reloaded */
u32 deny_unknown; /* current setting of deny_unknown */
};
When userspace object manager caches access control decisions provided
by SELinux, it needs to invalidate the cache on policy reload and setenforce
to keep consistency.
However, the applications need to check the kernel state for each accesses
on userspace avc, or launch a background worker process.
In heuristic, frequency of invalidation is much less than frequency of
making access control decision, so it is annoying to invoke a system call
to check we don't need to invalidate the userspace cache.
If we can use a background worker thread, it allows to receive invalidation
messages from the kernel. But it requires us an invasive coding toward the
base application in some cases; E.g, when we provide a feature performing
with SELinux as a plugin module, it is unwelcome manner to launch its own
worker thread from the module.
If we could map /selinux/status to process memory space, application can
know updates of selinux status; policy reload or setenforce.
A typical application checks selinux_kernel_status::sequence when it tries
to reference userspace avc. If it was changed from the last time when it
checked userspace avc, it means something was updated in the kernel space.
Then, the application can reset userspace avc or update current enforcing
mode, without any system call invocations.
This sequence number is updated according to the seqlock logic, so we need
to wait for a while if it is odd number.
Signed-off-by: KaiGai Kohei <kaigai@ak.jp.nec.com>
Acked-by: Eric Paris <eparis@redhat.com>
--
security/selinux/include/security.h | 21 ++++++
security/selinux/selinuxfs.c | 56 +++++++++++++++
security/selinux/ss/Makefile | 2 +-
security/selinux/ss/services.c | 3 +
security/selinux/ss/status.c | 129 +++++++++++++++++++++++++++++++++++
5 files changed, 210 insertions(+), 1 deletions(-)
Signed-off-by: James Morris <jmorris@namei.org>
2010-09-14 17:28:39 +08:00
|
|
|
[SEL_STATUS] = {"status", &sel_handle_status_ops, S_IRUGO},
|
2012-02-17 04:08:39 +08:00
|
|
|
[SEL_POLICY] = {"policy", &sel_policy_ops, S_IRUGO},
|
2015-12-25 00:09:41 +08:00
|
|
|
[SEL_VALIDATE_TRANS] = {"validatetrans", &sel_transition_ops,
|
|
|
|
S_IWUGO},
|
2005-04-17 06:20:36 +08:00
|
|
|
/* last one */ {""}
|
|
|
|
};
|
2018-03-20 23:59:11 +08:00
|
|
|
|
|
|
|
ret = selinux_fs_info_create(sb);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
ret = simple_fill_super(sb, SELINUX_MAGIC, selinux_files);
|
|
|
|
if (ret)
|
2006-03-22 16:09:17 +08:00
|
|
|
goto err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
fsi = sb->s_fs_info;
|
|
|
|
fsi->bool_dir = sel_make_dir(sb->s_root, BOOL_DIR_NAME, &fsi->last_ino);
|
|
|
|
if (IS_ERR(fsi->bool_dir)) {
|
|
|
|
ret = PTR_ERR(fsi->bool_dir);
|
|
|
|
fsi->bool_dir = NULL;
|
2006-03-22 16:09:17 +08:00
|
|
|
goto err;
|
2012-03-19 08:36:59 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
ret = -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
dentry = d_alloc_name(sb->s_root, NULL_FILE_NAME);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (!dentry)
|
2006-03-22 16:09:17 +08:00
|
|
|
goto err;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
ret = -ENOMEM;
|
2005-04-17 06:20:36 +08:00
|
|
|
inode = sel_make_inode(sb, S_IFCHR | S_IRUGO | S_IWUGO);
|
2018-08-05 17:10:36 +08:00
|
|
|
if (!inode) {
|
|
|
|
dput(dentry);
|
2006-03-22 16:09:17 +08:00
|
|
|
goto err;
|
2018-08-05 17:10:36 +08:00
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
inode->i_ino = ++fsi->last_ino;
|
2018-09-22 08:19:11 +08:00
|
|
|
isec = selinux_inode(inode);
|
2005-04-17 06:20:36 +08:00
|
|
|
isec->sid = SECINITSID_DEVNULL;
|
|
|
|
isec->sclass = SECCLASS_CHR_FILE;
|
2016-11-11 05:18:27 +08:00
|
|
|
isec->initialized = LABEL_INITIALIZED;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
init_special_inode(inode, S_IFCHR | S_IRUGO | S_IWUGO, MKDEV(MEM_MAJOR, 3));
|
|
|
|
d_add(dentry, inode);
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
dentry = sel_make_dir(sb->s_root, "avc", &fsi->last_ino);
|
2012-03-19 08:36:59 +08:00
|
|
|
if (IS_ERR(dentry)) {
|
|
|
|
ret = PTR_ERR(dentry);
|
2006-03-22 16:09:17 +08:00
|
|
|
goto err;
|
2012-03-19 08:36:59 +08:00
|
|
|
}
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
ret = sel_make_avc_files(dentry);
|
2019-11-22 17:33:06 +08:00
|
|
|
|
|
|
|
dentry = sel_make_dir(sb->s_root, "ss", &fsi->last_ino);
|
|
|
|
if (IS_ERR(dentry)) {
|
|
|
|
ret = PTR_ERR(dentry);
|
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = sel_make_ss_files(dentry);
|
2005-04-17 06:20:36 +08:00
|
|
|
if (ret)
|
2006-03-22 16:09:17 +08:00
|
|
|
goto err;
|
2007-04-04 22:11:29 +08:00
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
dentry = sel_make_dir(sb->s_root, "initial_contexts", &fsi->last_ino);
|
2012-03-19 08:36:59 +08:00
|
|
|
if (IS_ERR(dentry)) {
|
|
|
|
ret = PTR_ERR(dentry);
|
2007-04-04 22:11:29 +08:00
|
|
|
goto err;
|
2012-03-19 08:36:59 +08:00
|
|
|
}
|
2007-04-04 22:11:29 +08:00
|
|
|
|
|
|
|
ret = sel_make_initcon_files(dentry);
|
|
|
|
if (ret)
|
|
|
|
goto err;
|
|
|
|
|
2020-08-20 03:59:34 +08:00
|
|
|
fsi->class_dir = sel_make_dir(sb->s_root, CLASS_DIR_NAME, &fsi->last_ino);
|
2018-03-20 23:59:11 +08:00
|
|
|
if (IS_ERR(fsi->class_dir)) {
|
|
|
|
ret = PTR_ERR(fsi->class_dir);
|
|
|
|
fsi->class_dir = NULL;
|
2008-01-29 21:38:19 +08:00
|
|
|
goto err;
|
2012-03-19 08:36:59 +08:00
|
|
|
}
|
2008-01-29 21:38:19 +08:00
|
|
|
|
2020-08-20 03:59:34 +08:00
|
|
|
fsi->policycap_dir = sel_make_dir(sb->s_root, POLICYCAP_DIR_NAME,
|
2018-03-20 23:59:11 +08:00
|
|
|
&fsi->last_ino);
|
|
|
|
if (IS_ERR(fsi->policycap_dir)) {
|
|
|
|
ret = PTR_ERR(fsi->policycap_dir);
|
|
|
|
fsi->policycap_dir = NULL;
|
2008-01-29 21:38:19 +08:00
|
|
|
goto err;
|
2012-03-19 08:36:59 +08:00
|
|
|
}
|
2018-03-20 23:59:11 +08:00
|
|
|
|
2020-08-07 21:29:34 +08:00
|
|
|
ret = sel_make_policycap(fsi);
|
|
|
|
if (ret) {
|
|
|
|
pr_err("SELinux: failed to load policy capabilities\n");
|
2018-03-20 23:59:11 +08:00
|
|
|
goto err;
|
2020-08-07 21:29:34 +08:00
|
|
|
}
|
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
return 0;
|
2006-03-22 16:09:17 +08:00
|
|
|
err:
|
2018-06-12 16:09:06 +08:00
|
|
|
pr_err("SELinux: %s: failed while creating inodes\n",
|
2008-04-17 23:52:44 +08:00
|
|
|
__func__);
|
2018-03-20 23:59:11 +08:00
|
|
|
|
|
|
|
selinux_fs_info_free(sb);
|
|
|
|
|
2010-11-24 00:40:08 +08:00
|
|
|
return ret;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2019-03-26 00:38:30 +08:00
|
|
|
static int sel_get_tree(struct fs_context *fc)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2019-03-26 00:38:30 +08:00
|
|
|
return get_tree_single(fc, sel_fill_super);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct fs_context_operations sel_context_ops = {
|
|
|
|
.get_tree = sel_get_tree,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int sel_init_fs_context(struct fs_context *fc)
|
|
|
|
{
|
|
|
|
fc->ops = &sel_context_ops;
|
|
|
|
return 0;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2018-03-20 23:59:11 +08:00
|
|
|
static void sel_kill_sb(struct super_block *sb)
|
|
|
|
{
|
|
|
|
selinux_fs_info_free(sb);
|
|
|
|
kill_litter_super(sb);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
static struct file_system_type sel_fs_type = {
|
|
|
|
.name = "selinuxfs",
|
2019-03-26 00:38:30 +08:00
|
|
|
.init_fs_context = sel_init_fs_context,
|
2018-03-20 23:59:11 +08:00
|
|
|
.kill_sb = sel_kill_sb,
|
2005-04-17 06:20:36 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
struct vfsmount *selinuxfs_mount;
|
2018-03-20 23:59:11 +08:00
|
|
|
struct path selinux_null;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
static int __init init_sel_fs(void)
|
|
|
|
{
|
2018-03-20 23:59:11 +08:00
|
|
|
struct qstr null_name = QSTR_INIT(NULL_FILE_NAME,
|
|
|
|
sizeof(NULL_FILE_NAME)-1);
|
2005-04-17 06:20:36 +08:00
|
|
|
int err;
|
|
|
|
|
2019-12-17 22:15:10 +08:00
|
|
|
if (!selinux_enabled_boot)
|
2005-04-17 06:20:36 +08:00
|
|
|
return 0;
|
2011-05-11 06:34:16 +08:00
|
|
|
|
2015-05-14 06:35:41 +08:00
|
|
|
err = sysfs_create_mount_point(fs_kobj, "selinux");
|
|
|
|
if (err)
|
|
|
|
return err;
|
2011-05-11 06:34:16 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
err = register_filesystem(&sel_fs_type);
|
2011-05-11 06:34:16 +08:00
|
|
|
if (err) {
|
2015-05-14 06:35:41 +08:00
|
|
|
sysfs_remove_mount_point(fs_kobj, "selinux");
|
2010-11-24 00:40:08 +08:00
|
|
|
return err;
|
2011-05-11 06:34:16 +08:00
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2012-06-27 01:58:53 +08:00
|
|
|
selinux_null.mnt = selinuxfs_mount = kern_mount(&sel_fs_type);
|
2010-11-24 00:40:08 +08:00
|
|
|
if (IS_ERR(selinuxfs_mount)) {
|
2018-06-12 16:09:06 +08:00
|
|
|
pr_err("selinuxfs: could not mount!\n");
|
2010-11-24 00:40:08 +08:00
|
|
|
err = PTR_ERR(selinuxfs_mount);
|
|
|
|
selinuxfs_mount = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2018-03-20 23:59:11 +08:00
|
|
|
selinux_null.dentry = d_hash_and_lookup(selinux_null.mnt->mnt_root,
|
|
|
|
&null_name);
|
|
|
|
if (IS_ERR(selinux_null.dentry)) {
|
|
|
|
pr_err("selinuxfs: could not lookup null!\n");
|
|
|
|
err = PTR_ERR(selinux_null.dentry);
|
|
|
|
selinux_null.dentry = NULL;
|
|
|
|
}
|
2010-11-24 00:40:08 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
__initcall(init_sel_fs);
|
|
|
|
|
|
|
|
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
|
|
|
|
void exit_sel_fs(void)
|
|
|
|
{
|
2015-05-14 06:35:41 +08:00
|
|
|
sysfs_remove_mount_point(fs_kobj, "selinux");
|
2018-04-10 02:36:05 +08:00
|
|
|
dput(selinux_null.dentry);
|
2011-07-20 00:32:38 +08:00
|
|
|
kern_unmount(selinuxfs_mount);
|
2005-04-17 06:20:36 +08:00
|
|
|
unregister_filesystem(&sel_fs_type);
|
|
|
|
}
|
|
|
|
#endif
|