Commit b1dba247 authored by Linus Torvalds's avatar Linus Torvalds

Merge tag 'selinux-pr-20200127' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux

Pull SELinux update from Paul Moore:
 "This is one of the bigger SELinux pull requests in recent years with
  28 patches. Everything is passing our test suite and the highlights
  are below:

   - Mark CONFIG_SECURITY_SELINUX_DISABLE as deprecated. We're some time
     away from actually attempting to remove this in the kernel, but the
     only distro we know that still uses it (Fedora) is working on
     moving away from this so we want to at least let people know we are
     planning to remove it.

   - Reorder the SELinux hooks to help prevent bad things when SELinux
     is disabled at runtime. The proper fix is to remove the
     CONFIG_SECURITY_SELINUX_DISABLE functionality (see above) and just
     take care of it at boot time (e.g. "selinux=0").

   - Add SELinux controls for the kernel lockdown functionality,
     introducing a new SELinux class/permissions: "lockdown { integrity
     confidentiality }".

   - Add a SELinux control for move_mount(2) that reuses the "file {
     mounton }" permission.

   - Improvements to the SELinux security label data store lookup
     functions to speed up translations between our internal label
     representations and the visible string labels (both directions).

   - Revisit a previous fix related to SELinux inode auditing and
     permission caching and do it correctly this time.

   - Fix the SELinux access decision cache to cleanup properly on error.
     In some extreme cases this could limit the cache size and result in
     a decrease in performance.

   - Enable SELinux per-file labeling for binderfs.

   - The SELinux initialized and disabled flags were wrapped with
     accessors to ensure they are accessed correctly.

   - Mark several key SELinux structures with __randomize_layout.

   - Changes to the LSM build configuration to only build
     security/lsm_audit.c when needed.

   - Changes to the SELinux build configuration to only build the IB
     object cache when CONFIG_SECURITY_INFINIBAND is enabled.

   - Move a number of single-caller functions into their callers.

   - Documentation fixes (/selinux -> /sys/fs/selinux).

   - A handful of cleanup patches that aren't worth mentioning on their
     own, the individual descriptions have plenty of detail"

* tag 'selinux-pr-20200127' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux: (28 commits)
  selinux: fix regression introduced by move_mount(2) syscall
  selinux: do not allocate ancillary buffer on first load
  selinux: remove redundant allocation and helper functions
  selinux: remove redundant selinux_nlmsg_perm
  selinux: fix wrong buffer types in policydb.c
  selinux: reorder hooks to make runtime disable less broken
  selinux: treat atomic flags more carefully
  selinux: make default_noexec read-only after init
  selinux: move ibpkeys code under CONFIG_SECURITY_INFINIBAND.
  selinux: remove redundant msg_msg_alloc_security
  Documentation,selinux: fix references to old selinuxfs mount point
  selinux: deprecate disabling SELinux and runtime
  selinux: allow per-file labelling for binderfs
  selinuxfs: use scnprintf to get real length for inode
  selinux: remove set but not used variable 'sidtab'
  selinux: ensure the policy has been loaded before reading the sidtab stats
  selinux: ensure we cleanup the internal AVC counters on error in avc_update()
  selinux: randomize layout of key structures
  selinux: clean up selinux_enabled/disabled/enforcing_boot
  selinux: remove unnecessary selinux cred request
  ...
parents 07e309a9 98aa0034
What: /sys/fs/selinux/disable
Date: April 2005 (predates git)
KernelVersion: 2.6.12-rc2 (predates git)
Contact: selinux@vger.kernel.org
Description:
The selinuxfs "disable" node allows SELinux to be disabled at runtime
prior to a policy being loaded into the kernel. If disabled via this
mechanism, SELinux will remain disabled until the system is rebooted.
The preferred method of disabling SELinux is via the "selinux=0" boot
parameter, but the selinuxfs "disable" node was created to make it
easier for systems with primitive bootloaders that did not allow for
easy modification of the kernel command line. Unfortunately, allowing
for SELinux to be disabled at runtime makes it difficult to secure the
kernel's LSM hooks using the "__ro_after_init" feature.
Thankfully, the need for the SELinux runtime disable appears to be
gone, the default Kconfig configuration disables this selinuxfs node,
and only one of the major distributions, Fedora, supports disabling
SELinux at runtime. Fedora is in the process of removing the
selinuxfs "disable" node and once that is complete we will start the
slow process of removing this code from the kernel.
More information on /sys/fs/selinux/disable can be found under the
CONFIG_SECURITY_SELINUX_DISABLE Kconfig option.
......@@ -511,7 +511,7 @@
1 -- check protection requested by application.
Default value is set via a kernel config option.
Value can be changed at runtime via
/selinux/checkreqprot.
/sys/fs/selinux/checkreqprot.
cio_ignore= [S390]
See Documentation/s390/common_io.rst for details.
......@@ -1245,7 +1245,8 @@
0 -- permissive (log only, no denials).
1 -- enforcing (deny and log).
Default value is 0.
Value can be changed at runtime via /selinux/enforce.
Value can be changed at runtime via
/sys/fs/selinux/enforce.
erst_disable [ACPI]
Disable Error Record Serialization Table (ERST)
......@@ -4348,9 +4349,7 @@
See security/selinux/Kconfig help text.
0 -- disable.
1 -- enable.
Default value is set via kernel config option.
If enabled at boot time, /selinux/disable can be used
later to disable prior to initial policy load.
Default value is 1.
apparmor= [APPARMOR] Disable or enable AppArmor at boot time
Format: { "0" | "1" }
......
......@@ -14870,6 +14870,7 @@ F: include/uapi/linux/selinux_netlink.h
F: security/selinux/
F: scripts/selinux/
F: Documentation/admin-guide/LSM/SELinux.rst
F: Documentation/ABI/obsolete/sysfs-selinux-disable
SENSABLE PHANTOM
M: Jiri Slaby <jirislaby@gmail.com>
......
......@@ -74,6 +74,7 @@ struct common_audit_data {
#define LSM_AUDIT_DATA_FILE 12
#define LSM_AUDIT_DATA_IBPKEY 13
#define LSM_AUDIT_DATA_IBENDPORT 14
#define LSM_AUDIT_DATA_LOCKDOWN 15
union {
struct path path;
struct dentry *dentry;
......@@ -93,6 +94,7 @@ struct common_audit_data {
struct file *file;
struct lsm_ibpkey_audit *ibpkey;
struct lsm_ibendport_audit *ibendport;
int reason;
} u;
/* this union contains LSM specific data */
union {
......
......@@ -128,6 +128,8 @@ enum lockdown_reason {
LOCKDOWN_CONFIDENTIALITY_MAX,
};
extern const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1];
/* These functions are in security/commoncap.c */
extern int cap_capable(const struct cred *cred, struct user_namespace *ns,
int cap, unsigned int opts);
......
......@@ -22,7 +22,7 @@ obj-$(CONFIG_SECURITY) += security.o
obj-$(CONFIG_SECURITYFS) += inode.o
obj-$(CONFIG_SECURITY_SELINUX) += selinux/
obj-$(CONFIG_SECURITY_SMACK) += smack/
obj-$(CONFIG_AUDIT) += lsm_audit.o
obj-$(CONFIG_SECURITY) += lsm_audit.o
obj-$(CONFIG_SECURITY_TOMOYO) += tomoyo/
obj-$(CONFIG_SECURITY_APPARMOR) += apparmor/
obj-$(CONFIG_SECURITY_YAMA) += yama/
......
......@@ -16,33 +16,6 @@
static enum lockdown_reason kernel_locked_down;
static const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1] = {
[LOCKDOWN_NONE] = "none",
[LOCKDOWN_MODULE_SIGNATURE] = "unsigned module loading",
[LOCKDOWN_DEV_MEM] = "/dev/mem,kmem,port",
[LOCKDOWN_EFI_TEST] = "/dev/efi_test access",
[LOCKDOWN_KEXEC] = "kexec of unsigned images",
[LOCKDOWN_HIBERNATION] = "hibernation",
[LOCKDOWN_PCI_ACCESS] = "direct PCI access",
[LOCKDOWN_IOPORT] = "raw io port access",
[LOCKDOWN_MSR] = "raw MSR access",
[LOCKDOWN_ACPI_TABLES] = "modifying ACPI tables",
[LOCKDOWN_PCMCIA_CIS] = "direct PCMCIA CIS storage",
[LOCKDOWN_TIOCSSERIAL] = "reconfiguration of serial port IO",
[LOCKDOWN_MODULE_PARAMETERS] = "unsafe module parameters",
[LOCKDOWN_MMIOTRACE] = "unsafe mmio",
[LOCKDOWN_DEBUGFS] = "debugfs access",
[LOCKDOWN_XMON_WR] = "xmon write access",
[LOCKDOWN_INTEGRITY_MAX] = "integrity",
[LOCKDOWN_KCORE] = "/proc/kcore access",
[LOCKDOWN_KPROBES] = "use of kprobes",
[LOCKDOWN_BPF_READ] = "use of bpf to read kernel RAM",
[LOCKDOWN_PERF] = "unsafe use of perf",
[LOCKDOWN_TRACEFS] = "use of tracefs",
[LOCKDOWN_XMON_RW] = "xmon read and write access",
[LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality",
};
static const enum lockdown_reason lockdown_levels[] = {LOCKDOWN_NONE,
LOCKDOWN_INTEGRITY_MAX,
LOCKDOWN_CONFIDENTIALITY_MAX};
......
......@@ -27,6 +27,7 @@
#include <linux/dccp.h>
#include <linux/sctp.h>
#include <linux/lsm_audit.h>
#include <linux/security.h>
/**
* ipv4_skb_to_auditdata : fill auditdata from skb
......@@ -425,6 +426,10 @@ static void dump_common_audit_data(struct audit_buffer *ab,
a->u.ibendport->dev_name,
a->u.ibendport->port);
break;
case LSM_AUDIT_DATA_LOCKDOWN:
audit_log_format(ab, " lockdown_reason=");
audit_log_string(ab, lockdown_reasons[a->u.reason]);
break;
} /* switch (a->type) */
}
......
......@@ -35,6 +35,39 @@
#define LSM_COUNT (__end_lsm_info - __start_lsm_info)
#define EARLY_LSM_COUNT (__end_early_lsm_info - __start_early_lsm_info)
/*
* These are descriptions of the reasons that can be passed to the
* security_locked_down() LSM hook. Placing this array here allows
* all security modules to use the same descriptions for auditing
* purposes.
*/
const char *const lockdown_reasons[LOCKDOWN_CONFIDENTIALITY_MAX+1] = {
[LOCKDOWN_NONE] = "none",
[LOCKDOWN_MODULE_SIGNATURE] = "unsigned module loading",
[LOCKDOWN_DEV_MEM] = "/dev/mem,kmem,port",
[LOCKDOWN_EFI_TEST] = "/dev/efi_test access",
[LOCKDOWN_KEXEC] = "kexec of unsigned images",
[LOCKDOWN_HIBERNATION] = "hibernation",
[LOCKDOWN_PCI_ACCESS] = "direct PCI access",
[LOCKDOWN_IOPORT] = "raw io port access",
[LOCKDOWN_MSR] = "raw MSR access",
[LOCKDOWN_ACPI_TABLES] = "modifying ACPI tables",
[LOCKDOWN_PCMCIA_CIS] = "direct PCMCIA CIS storage",
[LOCKDOWN_TIOCSSERIAL] = "reconfiguration of serial port IO",
[LOCKDOWN_MODULE_PARAMETERS] = "unsafe module parameters",
[LOCKDOWN_MMIOTRACE] = "unsafe mmio",
[LOCKDOWN_DEBUGFS] = "debugfs access",
[LOCKDOWN_XMON_WR] = "xmon write access",
[LOCKDOWN_INTEGRITY_MAX] = "integrity",
[LOCKDOWN_KCORE] = "/proc/kcore access",
[LOCKDOWN_KPROBES] = "use of kprobes",
[LOCKDOWN_BPF_READ] = "use of bpf to read kernel RAM",
[LOCKDOWN_PERF] = "unsafe use of perf",
[LOCKDOWN_TRACEFS] = "use of tracefs",
[LOCKDOWN_XMON_RW] = "xmon read and write access",
[LOCKDOWN_CONFIDENTIALITY_MAX] = "confidentiality",
};
struct security_hook_heads security_hook_heads __lsm_ro_after_init;
static BLOCKING_NOTIFIER_HEAD(blocking_lsm_notifier_chain);
......
......@@ -42,6 +42,9 @@ config SECURITY_SELINUX_DISABLE
using the selinux=0 boot parameter instead of enabling this
option.
WARNING: this option is deprecated and will be removed in a future
kernel release.
If you are unsure how to answer this question, answer N.
config SECURITY_SELINUX_DEVELOP
......@@ -55,7 +58,8 @@ config SECURITY_SELINUX_DEVELOP
kernel will start in permissive mode (log everything, deny nothing)
unless you specify enforcing=1 on the kernel command line. You
can interactively toggle the kernel between enforcing mode and
permissive mode (if permitted by the policy) via /selinux/enforce.
permissive mode (if permitted by the policy) via
/sys/fs/selinux/enforce.
config SECURITY_SELINUX_AVC_STATS
bool "NSA SELinux AVC Statistics"
......@@ -63,7 +67,7 @@ config SECURITY_SELINUX_AVC_STATS
default y
help
This option collects access vector cache statistics to
/selinux/avc/cache_stats, which may be monitored via
/sys/fs/selinux/avc/cache_stats, which may be monitored via
tools such as avcstat.
config SECURITY_SELINUX_CHECKREQPROT_VALUE
......@@ -82,6 +86,29 @@ config SECURITY_SELINUX_CHECKREQPROT_VALUE
default to checking the protection requested by the application.
The checkreqprot flag may be changed from the default via the
'checkreqprot=' boot parameter. It may also be changed at runtime
via /selinux/checkreqprot if authorized by policy.
via /sys/fs/selinux/checkreqprot if authorized by policy.
If you are unsure how to answer this question, answer 0.
config SECURITY_SELINUX_SIDTAB_HASH_BITS
int "NSA SELinux sidtab hashtable size"
depends on SECURITY_SELINUX
range 8 13
default 9
help
This option sets the number of buckets used in the sidtab hashtable
to 2^SECURITY_SELINUX_SIDTAB_HASH_BITS buckets. The number of hash
collisions may be viewed at /sys/fs/selinux/ss/sidtab_hash_stats. If
chain lengths are high (e.g. > 20) then selecting a higher value here
will ensure that lookups times are short and stable.
config SECURITY_SELINUX_SID2STR_CACHE_SIZE
int "NSA SELinux SID to context string translation cache size"
depends on SECURITY_SELINUX
default 256
help
This option defines the size of the internal SID -> context string
cache, which improves the performance of context to string
conversion. Setting this option to 0 disables the cache completely.
If unsure, keep the default value.
......@@ -6,7 +6,7 @@
obj-$(CONFIG_SECURITY_SELINUX) := selinux.o
selinux-y := avc.o hooks.o selinuxfs.o netlink.o nlmsgtab.o netif.o \
netnode.o netport.o ibpkey.o \
netnode.o netport.o \
ss/ebitmap.o ss/hashtab.o ss/symtab.o ss/sidtab.o ss/avtab.o \
ss/policydb.o ss/services.o ss/conditional.o ss/mls.o ss/status.o
......@@ -14,6 +14,8 @@ selinux-$(CONFIG_SECURITY_NETWORK_XFRM) += xfrm.o
selinux-$(CONFIG_NETLABEL) += netlabel.o
selinux-$(CONFIG_SECURITY_INFINIBAND) += ibpkey.o
ccflags-y := -I$(srctree)/security/selinux -I$(srctree)/security/selinux/include
$(addprefix $(obj)/,$(selinux-y)): $(obj)/flask.h
......
......@@ -424,7 +424,7 @@ static inline int avc_xperms_audit(struct selinux_state *state,
if (likely(!audited))
return 0;
return slow_avc_audit(state, ssid, tsid, tclass, requested,
audited, denied, result, ad, 0);
audited, denied, result, ad);
}
static void avc_node_free(struct rcu_head *rhead)
......@@ -617,40 +617,37 @@ static struct avc_node *avc_insert(struct selinux_avc *avc,
struct avc_node *pos, *node = NULL;
int hvalue;
unsigned long flag;
spinlock_t *lock;
struct hlist_head *head;
if (avc_latest_notif_update(avc, avd->seqno, 1))
goto out;
return NULL;
node = avc_alloc_node(avc);
if (node) {
struct hlist_head *head;
spinlock_t *lock;
int rc = 0;
hvalue = avc_hash(ssid, tsid, tclass);
avc_node_populate(node, ssid, tsid, tclass, avd);
rc = avc_xperms_populate(node, xp_node);
if (rc) {
kmem_cache_free(avc_node_cachep, node);
return NULL;
}
head = &avc->avc_cache.slots[hvalue];
lock = &avc->avc_cache.slots_lock[hvalue];
if (!node)
return NULL;
spin_lock_irqsave(lock, flag);
hlist_for_each_entry(pos, head, list) {
if (pos->ae.ssid == ssid &&
pos->ae.tsid == tsid &&
pos->ae.tclass == tclass) {
avc_node_replace(avc, node, pos);
goto found;
}
avc_node_populate(node, ssid, tsid, tclass, avd);
if (avc_xperms_populate(node, xp_node)) {
avc_node_kill(avc, node);
return NULL;
}
hvalue = avc_hash(ssid, tsid, tclass);
head = &avc->avc_cache.slots[hvalue];
lock = &avc->avc_cache.slots_lock[hvalue];
spin_lock_irqsave(lock, flag);
hlist_for_each_entry(pos, head, list) {
if (pos->ae.ssid == ssid &&
pos->ae.tsid == tsid &&
pos->ae.tclass == tclass) {
avc_node_replace(avc, node, pos);
goto found;
}
hlist_add_head_rcu(&node->list, head);
found:
spin_unlock_irqrestore(lock, flag);
}
out:
hlist_add_head_rcu(&node->list, head);
found:
spin_unlock_irqrestore(lock, flag);
return node;
}
......@@ -758,8 +755,7 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
noinline int slow_avc_audit(struct selinux_state *state,
u32 ssid, u32 tsid, u16 tclass,
u32 requested, u32 audited, u32 denied, int result,
struct common_audit_data *a,
unsigned int flags)
struct common_audit_data *a)
{
struct common_audit_data stack_data;
struct selinux_audit_data sad;
......@@ -772,17 +768,6 @@ noinline int slow_avc_audit(struct selinux_state *state,
a->type = LSM_AUDIT_DATA_NONE;
}
/*
* When in a RCU walk do the audit on the RCU retry. This is because
* the collection of the dname in an inode audit message is not RCU
* safe. Note this may drop some audits when the situation changes
* during retry. However this is logically just as if the operation
* happened a little later.
*/
if ((a->type == LSM_AUDIT_DATA_INODE) &&
(flags & MAY_NOT_BLOCK))
return -ECHILD;
sad.tclass = tclass;
sad.requested = requested;
sad.ssid = ssid;
......@@ -855,15 +840,14 @@ static int avc_update_node(struct selinux_avc *avc,
/*
* If we are in a non-blocking code path, e.g. VFS RCU walk,
* then we must not add permissions to a cache entry
* because we cannot safely audit the denial. Otherwise,
* because we will not audit the denial. Otherwise,
* during the subsequent blocking retry (e.g. VFS ref walk), we
* will find the permissions already granted in the cache entry
* and won't audit anything at all, leading to silent denials in
* permissive mode that only appear when in enforcing mode.
*
* See the corresponding handling in slow_avc_audit(), and the
* logic in selinux_inode_permission for the MAY_NOT_BLOCK flag,
* which is transliterated into AVC_NONBLOCKING.
* See the corresponding handling of MAY_NOT_BLOCK in avc_audit()
* and selinux_inode_permission().
*/
if (flags & AVC_NONBLOCKING)
return 0;
......@@ -907,7 +891,7 @@ static int avc_update_node(struct selinux_avc *avc,
if (orig->ae.xp_node) {
rc = avc_xperms_populate(node, orig->ae.xp_node);
if (rc) {
kmem_cache_free(avc_node_cachep, node);
avc_node_kill(avc, node);
goto out_unlock;
}
}
......@@ -1205,6 +1189,25 @@ int avc_has_perm(struct selinux_state *state, u32 ssid, u32 tsid, u16 tclass,
return rc;
}
int avc_has_perm_flags(struct selinux_state *state,
u32 ssid, u32 tsid, u16 tclass, u32 requested,
struct common_audit_data *auditdata,
int flags)
{
struct av_decision avd;
int rc, rc2;
rc = avc_has_perm_noaudit(state, ssid, tsid, tclass, requested,
(flags & MAY_NOT_BLOCK) ? AVC_NONBLOCKING : 0,
&avd);
rc2 = avc_audit(state, ssid, tsid, tclass, requested, &avd, rc,
auditdata, flags);
if (rc2)
return rc2;
return rc;
}
u32 avc_policy_seqno(struct selinux_state *state)
{
return state->avc->avc_cache.latest_notif;
......
......@@ -109,7 +109,7 @@ struct selinux_state selinux_state;
static atomic_t selinux_secmark_refcount = ATOMIC_INIT(0);
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
static int selinux_enforcing_boot;
static int selinux_enforcing_boot __initdata;
static int __init enforcing_setup(char *str)
{
......@@ -123,13 +123,13 @@ __setup("enforcing=", enforcing_setup);
#define selinux_enforcing_boot 1
#endif
int selinux_enabled __lsm_ro_after_init = 1;
int selinux_enabled_boot __initdata = 1;
#ifdef CONFIG_SECURITY_SELINUX_BOOTPARAM
static int __init selinux_enabled_setup(char *str)
{
unsigned long enabled;
if (!kstrtoul(str, 0, &enabled))
selinux_enabled = enabled ? 1 : 0;
selinux_enabled_boot = enabled ? 1 : 0;
return 1;
}
__setup("selinux=", selinux_enabled_setup);
......@@ -238,24 +238,6 @@ static inline u32 task_sid(const struct task_struct *task)
return sid;
}
/* Allocate and free functions for each kind of security blob. */
static int inode_alloc_security(struct inode *inode)
{
struct inode_security_struct *isec = selinux_inode(inode);
u32 sid = current_sid();
spin_lock_init(&isec->lock);
INIT_LIST_HEAD(&isec->list);
isec->inode = inode;
isec->sid = SECINITSID_UNLABELED;
isec->sclass = SECCLASS_FILE;
isec->task_sid = sid;
isec->initialized = LABEL_INVALID;
return 0;
}
static int inode_doinit_with_dentry(struct inode *inode, struct dentry *opt_dentry);
/*
......@@ -272,7 +254,7 @@ static int __inode_security_revalidate(struct inode *inode,
might_sleep_if(may_sleep);
if (selinux_state.initialized &&
if (selinux_initialized(&selinux_state) &&
isec->initialized != LABEL_INITIALIZED) {
if (!may_sleep)
return -ECHILD;
......@@ -354,37 +336,6 @@ static void inode_free_security(struct inode *inode)
}
}
static int file_alloc_security(struct file *file)
{
struct file_security_struct *fsec = selinux_file(file);
u32 sid = current_sid();
fsec->sid = sid;
fsec->fown_sid = sid;
return 0;
}
static int superblock_alloc_security(struct super_block *sb)
{
struct superblock_security_struct *sbsec;
sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_KERNEL);
if (!sbsec)
return -ENOMEM;
mutex_init(&sbsec->lock);
INIT_LIST_HEAD(&sbsec->isec_head);
spin_lock_init(&sbsec->isec_lock);
sbsec->sb = sb;
sbsec->sid = SECINITSID_UNLABELED;
sbsec->def_sid = SECINITSID_FILE;
sbsec->mntpoint_sid = SECINITSID_UNLABELED;
sb->s_security = sbsec;
return 0;
}
static void superblock_free_security(struct super_block *sb)
{
struct superblock_security_struct *sbsec = sb->s_security;
......@@ -406,11 +357,6 @@ static void selinux_free_mnt_opts(void *mnt_opts)
kfree(opts);
}
static inline int inode_doinit(struct inode *inode)
{
return inode_doinit_with_dentry(inode, NULL);
}
enum {
Opt_error = -1,
Opt_context = 0,
......@@ -598,7 +544,7 @@ static int sb_finish_set_opts(struct super_block *sb)
inode = igrab(inode);
if (inode) {
if (!IS_PRIVATE(inode))
inode_doinit(inode);
inode_doinit_with_dentry(inode, NULL);
iput(inode);
}
spin_lock(&sbsec->isec_lock);
......@@ -659,7 +605,7 @@ static int selinux_set_mnt_opts(struct super_block *sb,
mutex_lock(&sbsec->lock);
if (!selinux_state.initialized) {
if (!selinux_initialized(&selinux_state)) {
if (!opts) {
/* Defer initialization until selinux_complete_init,
after the initial policy is loaded and the security
......@@ -752,6 +698,7 @@ static int selinux_set_mnt_opts(struct super_block *sb,
if (!strcmp(sb->s_type->name, "debugfs") ||
!strcmp(sb->s_type->name, "tracefs") ||
!strcmp(sb->s_type->name, "binderfs") ||
!strcmp(sb->s_type->name, "pstore"))
sbsec->flags |= SE_SBGENFS;
......@@ -928,7 +875,7 @@ static int selinux_sb_clone_mnt_opts(const struct super_block *oldsb,
* if the parent was able to be mounted it clearly had no special lsm
* mount options. thus we can safely deal with this superblock later
*/
if (!selinux_state.initialized)
if (!selinux_initialized(&selinux_state))
return 0;
/*
......@@ -1103,7 +1050,7 @@ static int selinux_sb_show_options(struct seq_file *m, struct super_block *sb)
if (!(sbsec->flags & SE_SBINITIALIZED))
return 0;
if (!selinux_state.initialized)
if (!selinux_initialized(&selinux_state))
return 0;
if (sbsec->flags & FSCONTEXT_MNT) {
......@@ -1833,8 +1780,8 @@ static int may_create(struct inode *dir,
if (rc)
return rc;
rc = selinux_determine_inode_label(selinux_cred(current_cred()), dir,
&dentry->d_name, tclass, &newsid);
rc = selinux_determine_inode_label(tsec, dir, &dentry->d_name, tclass,
&newsid);
if (rc)
return rc;
......@@ -2592,7 +2539,22 @@ static void selinux_bprm_committed_creds(struct linux_binprm *bprm)
static int selinux_sb_alloc_security(struct super_block *sb)
{
return superblock_alloc_security(sb);
struct superblock_security_struct *sbsec;
sbsec = kzalloc(sizeof(struct superblock_security_struct), GFP_KERNEL);
if (!sbsec)
return -ENOMEM;
mutex_init(&sbsec->lock);
INIT_LIST_HEAD(&sbsec->isec_head);
spin_lock_init(&sbsec->isec_lock);
sbsec->sb = sb;
sbsec->sid = SECINITSID_UNLABELED;
sbsec->def_sid = SECINITSID_FILE;
sbsec->mntpoint_sid = SECINITSID_UNLABELED;
sb->s_security = sbsec;
return 0;
}
static void selinux_sb_free_security(struct super_block *sb)
......@@ -2762,6 +2724,14 @@ static int selinux_mount(const char *dev_name,
return path_has_perm(cred, path, FILE__MOUNTON);
}
static int selinux_move_mount(const struct path *from_path,
const struct path *to_path)
{
const struct cred *cred = current_cred();
return path_has_perm(cred, to_path, FILE__MOUNTON);
}
static int selinux_umount(struct vfsmount *mnt, int flags)
{
const struct cred *cred = current_cred();
......@@ -2844,7 +2814,18 @@ static int selinux_fs_context_parse_param(struct fs_context *fc,
static int selinux_inode_alloc_security(struct inode *inode)
{
return inode_alloc_security(inode);
struct inode_security_struct *isec = selinux_inode(inode);
u32 sid = current_sid();
spin_lock_init(&isec->lock);
INIT_LIST_HEAD(&isec->list);
isec->inode = inode;
isec->sid = SECINITSID_UNLABELED;
isec->sclass = SECCLASS_FILE;
isec->task_sid = sid;
isec->initialized = LABEL_INVALID;
return 0;
}
static void selinux_inode_free_security(struct inode *inode)
......@@ -2906,8 +2887,7 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
newsid = tsec->create_sid;
rc = selinux_determine_inode_label(selinux_cred(current_cred()),
dir, qstr,
rc = selinux_determine_inode_label(tsec, dir, qstr,
inode_mode_to_security_class(inode->i_mode),
&newsid);
if (rc)
......@@ -2921,7 +2901,8 @@ static int selinux_inode_init_security(struct inode *inode, struct inode *dir,
isec->initialized = LABEL_INITIALIZED;
}
if (!selinux_state.initialized || !(sbsec->flags & SBLABEL_MNT))
if (!selinux_initialized(&selinux_state) ||
!(sbsec->flags & SBLABEL_MNT))
return -EOPNOTSUPP;
if (name)
......@@ -3004,14 +2985,14 @@ static int selinux_inode_follow_link(struct dentry *dentry, struct inode *inode,
if (IS_ERR(isec))
return PTR_ERR(isec);
return avc_has_perm(&selinux_state,
sid, isec->sid, isec->sclass, FILE__READ, &ad);
return avc_has_perm_flags(&selinux_state,
sid, isec->sid, isec->sclass, FILE__READ, &ad,
rcu ? MAY_NOT_BLOCK : 0);
}
static noinline int audit_inode_permission(struct inode *inode,
u32 perms, u32 audited, u32 denied,
int result,
unsigned flags)
int result)
{
struct common_audit_data ad;
struct inode_security_struct *isec = selinux_inode(inode);
......@@ -3022,7 +3003,7 @@ static noinline int audit_inode_permission(struct inode *inode,
rc = slow_avc_audit(&selinux_state,
current_sid(), isec->sid, isec->sclass, perms,
audited, denied, result, &ad, flags);
audited, denied, result, &ad);
if (rc)
return rc;
return 0;
......@@ -3033,7 +3014,7 @@ static int selinux_inode_permission(struct inode *inode, int mask)
const struct cred *cred = current_cred();
u32 perms;
bool from_access;
unsigned flags = mask & MAY_NOT_BLOCK;
bool no_block = mask & MAY_NOT_BLOCK;
struct inode_security_struct *isec;
u32 sid;
struct av_decision avd;
......@@ -3055,13 +3036,13 @@ static int selinux_inode_permission(struct inode *inode, int mask)
perms = file_mask_to_av(inode->i_mode, mask);
sid = cred_sid(cred);
isec = inode_security_rcu(inode, flags & MAY_NOT_BLOCK);
isec = inode_security_rcu(inode, no_block);
if (IS_ERR(isec))
return PTR_ERR(isec);
rc = avc_has_perm_noaudit(&selinux_state,
sid, isec->sid, isec->sclass, perms,
(flags & MAY_NOT_BLOCK) ? AVC_NONBLOCKING : 0,
no_block ? AVC_NONBLOCKING : 0,
&avd);
audited = avc_audit_required(perms, &avd, rc,
from_access ? FILE__AUDIT_ACCESS : 0,
......@@ -3069,7 +3050,11 @@ static int selinux_inode_permission(struct inode *inode, int mask)
if (likely(!audited))
return rc;
rc2 = audit_inode_permission(inode, perms, audited, denied, rc, flags);
/* fall back to ref-walk if we have to generate audit */
if (no_block)
return -ECHILD;
rc2 = audit_inode_permission(inode, perms, audited, denied, rc);
if (rc2)
return rc2;
return rc;
......@@ -3140,7 +3125,7 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name,
return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
}
if (!selinux_state.initialized)
if (!selinux_initialized(&selinux_state))
return (inode_owner_or_capable(inode) ? 0 : -EPERM);
sbsec = inode->i_sb->s_security;
......@@ -3226,7 +3211,7 @@ static void selinux_inode_post_setxattr(struct dentry *dentry, const char *name,
return;
}
if (!selinux_state.initialized) {
if (!selinux_initialized(&selinux_state)) {
/* If we haven't even been initialized, then we can't validate
* against a policy, so leave the label as invalid. It may
* resolve to a valid label on the next revalidation try if
......@@ -3550,7 +3535,13 @@ static int selinux_file_permission(struct file *file, int mask)
static int selinux_file_alloc_security(struct file *file)
{
return file_alloc_security(file);
struct file_security_struct *fsec = selinux_file(file);
u32 sid = current_sid();
fsec->sid = sid;
fsec->fown_sid = sid;
return 0;
}
/*
......@@ -3643,7 +3634,7 @@ static int selinux_file_ioctl(struct file *file, unsigned int cmd,
return error;
}
static int default_noexec;
static int default_noexec __ro_after_init;
static int file_map_prot_check(struct file *file, unsigned long prot, int shared)
{
......@@ -5515,44 +5506,6 @@ static int selinux_tun_dev_open(void *security)
return 0;
}
static int selinux_nlmsg_perm(struct sock *sk, struct sk_buff *skb)
{
int err = 0;
u32 perm;
struct nlmsghdr *nlh;
struct sk_security_struct *sksec = sk->sk_security;
if (skb->len < NLMSG_HDRLEN) {
err = -EINVAL;
goto out;
}
nlh = nlmsg_hdr(skb);
err = selinux_nlmsg_lookup(sksec->sclass, nlh->nlmsg_type, &perm);
if (err) {
if (err == -EINVAL) {
pr_warn_ratelimited("SELinux: unrecognized netlink"
" message: protocol=%hu nlmsg_type=%hu sclass=%s"
" pig=%d comm=%s\n",
sk->sk_protocol, nlh->nlmsg_type,
secclass_map[sksec->sclass - 1].name,
task_pid_nr(current), current->comm);
if (!enforcing_enabled(&selinux_state) ||
security_get_allow_unknown(&selinux_state))
err = 0;
}
/* Ignore */
if (err == -ENOENT)
err = 0;
goto out;
}
err = sock_has_perm(sk, perm);
out:
return err;
}
#ifdef CONFIG_NETFILTER
static unsigned int selinux_ip_forward(struct sk_buff *skb,
......@@ -5881,7 +5834,40 @@ static unsigned int selinux_ipv6_postroute(void *priv,
static int selinux_netlink_send(struct sock *sk, struct sk_buff *skb)
{
return selinux_nlmsg_perm(sk, skb);
int err = 0;
u32 perm;
struct nlmsghdr *nlh;
struct sk_security_struct *sksec = sk->sk_security;
if (skb->len < NLMSG_HDRLEN) {
err = -EINVAL;
goto out;
}
nlh = nlmsg_hdr(skb);
err = selinux_nlmsg_lookup(sksec->sclass, nlh->nlmsg_type, &perm);
if (err) {
if (err == -EINVAL) {
pr_warn_ratelimited("SELinux: unrecognized netlink"
" message: protocol=%hu nlmsg_type=%hu sclass=%s"
" pid=%d comm=%s\n",
sk->sk_protocol, nlh->nlmsg_type,
secclass_map[sksec->sclass - 1].name,
task_pid_nr(current), current->comm);
if (!enforcing_enabled(&selinux_state) ||
security_get_allow_unknown(&selinux_state))
err = 0;
}
/* Ignore */
if (err == -ENOENT)
err = 0;
goto out;
}
err = sock_has_perm(sk, perm);
out:
return err;
}
static void ipc_init_security(struct ipc_security_struct *isec, u16 sclass)
......@@ -5890,16 +5876,6 @@ static void ipc_init_security(struct ipc_security_struct *isec, u16 sclass)
isec->sid = current_sid();
}
static int msg_msg_alloc_security(struct msg_msg *msg)
{
struct msg_security_struct *msec;
msec = selinux_msg_msg(msg);
msec->sid = SECINITSID_UNLABELED;
return 0;
}
static int ipc_has_perm(struct kern_ipc_perm *ipc_perms,
u32 perms)
{
......@@ -5918,7 +5894,12 @@ static int ipc_has_perm(struct kern_ipc_perm *ipc_perms,
static int selinux_msg_msg_alloc_security(struct msg_msg *msg)
{
return msg_msg_alloc_security(msg);
struct msg_security_struct *msec;
msec = selinux_msg_msg(msg);
msec->sid = SECINITSID_UNLABELED;
return 0;
}
/* message queue security operations */
......@@ -6795,6 +6776,34 @@ static void selinux_bpf_prog_free(struct bpf_prog_aux *aux)
}
#endif
static int selinux_lockdown(enum lockdown_reason what)
{
struct common_audit_data ad;
u32 sid = current_sid();
int invalid_reason = (what <= LOCKDOWN_NONE) ||
(what == LOCKDOWN_INTEGRITY_MAX) ||
(what >= LOCKDOWN_CONFIDENTIALITY_MAX);
if (WARN(invalid_reason, "Invalid lockdown reason")) {
audit_log(audit_context(),
GFP_ATOMIC, AUDIT_SELINUX_ERR,
"lockdown_reason=invalid");
return -EINVAL;
}
ad.type = LSM_AUDIT_DATA_LOCKDOWN;
ad.u.reason = what;
if (what <= LOCKDOWN_INTEGRITY_MAX)
return avc_has_perm(&selinux_state,
sid, sid, SECCLASS_LOCKDOWN,
LOCKDOWN__INTEGRITY, &ad);
else
return avc_has_perm(&selinux_state,
sid, sid, SECCLASS_LOCKDOWN,
LOCKDOWN__CONFIDENTIALITY, &ad);
}
struct lsm_blob_sizes selinux_blob_sizes __lsm_ro_after_init = {
.lbs_cred = sizeof(struct task_security_struct),
.lbs_file = sizeof(struct file_security_struct),
......@@ -6864,6 +6873,21 @@ static int selinux_perf_event_write(struct perf_event *event)
}
#endif
/*
* IMPORTANT NOTE: When adding new hooks, please be careful to keep this order:
* 1. any hooks that don't belong to (2.) or (3.) below,
* 2. hooks that both access structures allocated by other hooks, and allocate
* structures that can be later accessed by other hooks (mostly "cloning"
* hooks),
* 3. hooks that only allocate structures that can be later accessed by other
* hooks ("allocating" hooks).
*
* Please follow block comment delimiters in the list to keep this order.
*
* This ordering is needed for SELinux runtime disable to work at least somewhat
* safely. Breaking the ordering rules above might lead to NULL pointer derefs
* when disabling SELinux at runtime.
*/
static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(binder_set_context_mgr, selinux_binder_set_context_mgr),
LSM_HOOK_INIT(binder_transaction, selinux_binder_transaction),
......@@ -6886,12 +6910,7 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(bprm_committing_creds, selinux_bprm_committing_creds),
LSM_HOOK_INIT(bprm_committed_creds, selinux_bprm_committed_creds),
LSM_HOOK_INIT(fs_context_dup, selinux_fs_context_dup),
LSM_HOOK_INIT(fs_context_parse_param, selinux_fs_context_parse_param),
LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security),
LSM_HOOK_INIT(sb_free_security, selinux_sb_free_security),
LSM_HOOK_INIT(sb_eat_lsm_opts, selinux_sb_eat_lsm_opts),
LSM_HOOK_INIT(sb_free_mnt_opts, selinux_free_mnt_opts),
LSM_HOOK_INIT(sb_remount, selinux_sb_remount),
LSM_HOOK_INIT(sb_kern_mount, selinux_sb_kern_mount),
......@@ -6901,12 +6920,12 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(sb_umount, selinux_umount),
LSM_HOOK_INIT(sb_set_mnt_opts, selinux_set_mnt_opts),
LSM_HOOK_INIT(sb_clone_mnt_opts, selinux_sb_clone_mnt_opts),
LSM_HOOK_INIT(sb_add_mnt_opt, selinux_add_mnt_opt),
LSM_HOOK_INIT(move_mount, selinux_move_mount),
LSM_HOOK_INIT(dentry_init_security, selinux_dentry_init_security),
LSM_HOOK_INIT(dentry_create_files_as, selinux_dentry_create_files_as),
LSM_HOOK_INIT(inode_alloc_security, selinux_inode_alloc_security),
LSM_HOOK_INIT(inode_free_security, selinux_inode_free_security),
LSM_HOOK_INIT(inode_init_security, selinux_inode_init_security),
LSM_HOOK_INIT(inode_create, selinux_inode_create),
......@@ -6978,21 +6997,15 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(ipc_permission, selinux_ipc_permission),
LSM_HOOK_INIT(ipc_getsecid, selinux_ipc_getsecid),
LSM_HOOK_INIT(msg_msg_alloc_security, selinux_msg_msg_alloc_security),
LSM_HOOK_INIT(msg_queue_alloc_security,
selinux_msg_queue_alloc_security),
LSM_HOOK_INIT(msg_queue_associate, selinux_msg_queue_associate),
LSM_HOOK_INIT(msg_queue_msgctl, selinux_msg_queue_msgctl),
LSM_HOOK_INIT(msg_queue_msgsnd, selinux_msg_queue_msgsnd),
LSM_HOOK_INIT(msg_queue_msgrcv, selinux_msg_queue_msgrcv),
LSM_HOOK_INIT(shm_alloc_security, selinux_shm_alloc_security),
LSM_HOOK_INIT(shm_associate, selinux_shm_associate),
LSM_HOOK_INIT(shm_shmctl, selinux_shm_shmctl),
LSM_HOOK_INIT(shm_shmat, selinux_shm_shmat),
LSM_HOOK_INIT(sem_alloc_security, selinux_sem_alloc_security),
LSM_HOOK_INIT(sem_associate, selinux_sem_associate),
LSM_HOOK_INIT(sem_semctl, selinux_sem_semctl),
LSM_HOOK_INIT(sem_semop, selinux_sem_semop),
......@@ -7003,13 +7016,11 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(setprocattr, selinux_setprocattr),
LSM_HOOK_INIT(ismaclabel, selinux_ismaclabel),
LSM_HOOK_INIT(secid_to_secctx, selinux_secid_to_secctx),
LSM_HOOK_INIT(secctx_to_secid, selinux_secctx_to_secid),
LSM_HOOK_INIT(release_secctx, selinux_release_secctx),
LSM_HOOK_INIT(inode_invalidate_secctx, selinux_inode_invalidate_secctx),
LSM_HOOK_INIT(inode_notifysecctx, selinux_inode_notifysecctx),
LSM_HOOK_INIT(inode_setsecctx, selinux_inode_setsecctx),
LSM_HOOK_INIT(inode_getsecctx, selinux_inode_getsecctx),
LSM_HOOK_INIT(unix_stream_connect, selinux_socket_unix_stream_connect),
LSM_HOOK_INIT(unix_may_send, selinux_socket_unix_may_send),
......@@ -7032,7 +7043,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(socket_getpeersec_stream,
selinux_socket_getpeersec_stream),
LSM_HOOK_INIT(socket_getpeersec_dgram, selinux_socket_getpeersec_dgram),
LSM_HOOK_INIT(sk_alloc_security, selinux_sk_alloc_security),
LSM_HOOK_INIT(sk_free_security, selinux_sk_free_security),
LSM_HOOK_INIT(sk_clone_security, selinux_sk_clone_security),
LSM_HOOK_INIT(sk_getsecid, selinux_sk_getsecid),
......@@ -7047,7 +7057,6 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(secmark_refcount_inc, selinux_secmark_refcount_inc),
LSM_HOOK_INIT(secmark_refcount_dec, selinux_secmark_refcount_dec),
LSM_HOOK_INIT(req_classify_flow, selinux_req_classify_flow),
LSM_HOOK_INIT(tun_dev_alloc_security, selinux_tun_dev_alloc_security),
LSM_HOOK_INIT(tun_dev_free_security, selinux_tun_dev_free_security),
LSM_HOOK_INIT(tun_dev_create, selinux_tun_dev_create),
LSM_HOOK_INIT(tun_dev_attach_queue, selinux_tun_dev_attach_queue),
......@@ -7057,17 +7066,11 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(ib_pkey_access, selinux_ib_pkey_access),
LSM_HOOK_INIT(ib_endport_manage_subnet,
selinux_ib_endport_manage_subnet),
LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security),
LSM_HOOK_INIT(ib_free_security, selinux_ib_free_security),
#endif
#ifdef CONFIG_SECURITY_NETWORK_XFRM
LSM_HOOK_INIT(xfrm_policy_alloc_security, selinux_xfrm_policy_alloc),
LSM_HOOK_INIT(xfrm_policy_clone_security, selinux_xfrm_policy_clone),
LSM_HOOK_INIT(xfrm_policy_free_security, selinux_xfrm_policy_free),
LSM_HOOK_INIT(xfrm_policy_delete_security, selinux_xfrm_policy_delete),
LSM_HOOK_INIT(xfrm_state_alloc, selinux_xfrm_state_alloc),
LSM_HOOK_INIT(xfrm_state_alloc_acquire,
selinux_xfrm_state_alloc_acquire),
LSM_HOOK_INIT(xfrm_state_free_security, selinux_xfrm_state_free),
LSM_HOOK_INIT(xfrm_state_delete_security, selinux_xfrm_state_delete),
LSM_HOOK_INIT(xfrm_policy_lookup, selinux_xfrm_policy_lookup),
......@@ -7077,14 +7080,12 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
#endif
#ifdef CONFIG_KEYS
LSM_HOOK_INIT(key_alloc, selinux_key_alloc),
LSM_HOOK_INIT(key_free, selinux_key_free),
LSM_HOOK_INIT(key_permission, selinux_key_permission),
LSM_HOOK_INIT(key_getsecurity, selinux_key_getsecurity),
#endif
#ifdef CONFIG_AUDIT
LSM_HOOK_INIT(audit_rule_init, selinux_audit_rule_init),
LSM_HOOK_INIT(audit_rule_known, selinux_audit_rule_known),
LSM_HOOK_INIT(audit_rule_match, selinux_audit_rule_match),
LSM_HOOK_INIT(audit_rule_free, selinux_audit_rule_free),
......@@ -7094,19 +7095,66 @@ static struct security_hook_list selinux_hooks[] __lsm_ro_after_init = {
LSM_HOOK_INIT(bpf, selinux_bpf),
LSM_HOOK_INIT(bpf_map, selinux_bpf_map),
LSM_HOOK_INIT(bpf_prog, selinux_bpf_prog),
LSM_HOOK_INIT(bpf_map_alloc_security, selinux_bpf_map_alloc),
LSM_HOOK_INIT(bpf_prog_alloc_security, selinux_bpf_prog_alloc),
LSM_HOOK_INIT(bpf_map_free_security, selinux_bpf_map_free),
LSM_HOOK_INIT(bpf_prog_free_security, selinux_bpf_prog_free),
#endif
#ifdef CONFIG_PERF_EVENTS
LSM_HOOK_INIT(perf_event_open, selinux_perf_event_open),
LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc),
LSM_HOOK_INIT(perf_event_free, selinux_perf_event_free),
LSM_HOOK_INIT(perf_event_read, selinux_perf_event_read),
LSM_HOOK_INIT(perf_event_write, selinux_perf_event_write),
#endif
LSM_HOOK_INIT(locked_down, selinux_lockdown),
/*
* PUT "CLONING" (ACCESSING + ALLOCATING) HOOKS HERE
*/
LSM_HOOK_INIT(fs_context_dup, selinux_fs_context_dup),
LSM_HOOK_INIT(fs_context_parse_param, selinux_fs_context_parse_param),
LSM_HOOK_INIT(sb_eat_lsm_opts, selinux_sb_eat_lsm_opts),
LSM_HOOK_INIT(sb_add_mnt_opt, selinux_add_mnt_opt),
#ifdef CONFIG_SECURITY_NETWORK_XFRM
LSM_HOOK_INIT(xfrm_policy_clone_security, selinux_xfrm_policy_clone),
#endif
/*
* PUT "ALLOCATING" HOOKS HERE
*/
LSM_HOOK_INIT(msg_msg_alloc_security, selinux_msg_msg_alloc_security),
LSM_HOOK_INIT(msg_queue_alloc_security,
selinux_msg_queue_alloc_security),
LSM_HOOK_INIT(shm_alloc_security, selinux_shm_alloc_security),
LSM_HOOK_INIT(sb_alloc_security, selinux_sb_alloc_security),
LSM_HOOK_INIT(inode_alloc_security, selinux_inode_alloc_security),
LSM_HOOK_INIT(sem_alloc_security, selinux_sem_alloc_security),
LSM_HOOK_INIT(secid_to_secctx, selinux_secid_to_secctx),
LSM_HOOK_INIT(inode_getsecctx, selinux_inode_getsecctx),
LSM_HOOK_INIT(sk_alloc_security, selinux_sk_alloc_security),
LSM_HOOK_INIT(tun_dev_alloc_security, selinux_tun_dev_alloc_security),
#ifdef CONFIG_SECURITY_INFINIBAND
LSM_HOOK_INIT(ib_alloc_security, selinux_ib_alloc_security),
#endif
#ifdef CONFIG_SECURITY_NETWORK_XFRM
LSM_HOOK_INIT(xfrm_policy_alloc_security, selinux_xfrm_policy_alloc),
LSM_HOOK_INIT(xfrm_state_alloc, selinux_xfrm_state_alloc),
LSM_HOOK_INIT(xfrm_state_alloc_acquire,
selinux_xfrm_state_alloc_acquire),
#endif
#ifdef CONFIG_KEYS
LSM_HOOK_INIT(key_alloc, selinux_key_alloc),
#endif
#ifdef CONFIG_AUDIT
LSM_HOOK_INIT(audit_rule_init, selinux_audit_rule_init),
#endif
#ifdef CONFIG_BPF_SYSCALL
LSM_HOOK_INIT(bpf_map_alloc_security, selinux_bpf_map_alloc),
LSM_HOOK_INIT(bpf_prog_alloc_security, selinux_bpf_prog_alloc),
#endif
#ifdef CONFIG_PERF_EVENTS
LSM_HOOK_INIT(perf_event_alloc, selinux_perf_event_alloc),
#endif
};
static __init int selinux_init(void)
......@@ -7169,7 +7217,7 @@ void selinux_complete_init(void)
DEFINE_LSM(selinux) = {
.name = "selinux",
.flags = LSM_FLAG_LEGACY_MAJOR | LSM_FLAG_EXCLUSIVE,
.enabled = &selinux_enabled,
.enabled = &selinux_enabled_boot,
.blobs = &selinux_blob_sizes,
.init = selinux_init,
};
......@@ -7238,7 +7286,7 @@ static int __init selinux_nf_ip_init(void)
{
int err;
if (!selinux_enabled)
if (!selinux_enabled_boot)
return 0;
pr_debug("SELinux: Registering netfilter hooks\n");
......@@ -7271,30 +7319,32 @@ static void selinux_nf_ip_exit(void)
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
int selinux_disable(struct selinux_state *state)
{
if (state->initialized) {
if (selinux_initialized(state)) {
/* Not permitted after initial policy load. */
return -EINVAL;
}
if (state->disabled) {
if (selinux_disabled(state)) {
/* Only do this once. */
return -EINVAL;
}
state->disabled = 1;
selinux_mark_disabled(state);
pr_info("SELinux: Disabled at runtime.\n");
selinux_enabled = 0;
/*
* Unregister netfilter hooks.
* Must be done before security_delete_hooks() to avoid breaking
* runtime disable.
*/
selinux_nf_ip_exit();
security_delete_hooks(selinux_hooks, ARRAY_SIZE(selinux_hooks));
/* Try to destroy the avc node cache */
avc_disable();
/* Unregister netfilter hooks. */
selinux_nf_ip_exit();
/* Unregister selinuxfs. */
exit_sel_fs();
......
......@@ -222,7 +222,7 @@ static __init int sel_ib_pkey_init(void)
{
int iter;
if (!selinux_enabled)
if (!selinux_enabled_boot)
return 0;
for (iter = 0; iter < SEL_PKEY_HASH_SIZE; iter++) {
......
......@@ -100,8 +100,7 @@ static inline u32 avc_audit_required(u32 requested,
int slow_avc_audit(struct selinux_state *state,
u32 ssid, u32 tsid, u16 tclass,
u32 requested, u32 audited, u32 denied, int result,
struct common_audit_data *a,
unsigned flags);
struct common_audit_data *a);
/**
* avc_audit - Audit the granting or denial of permissions.
......@@ -135,9 +134,12 @@ static inline int avc_audit(struct selinux_state *state,
audited = avc_audit_required(requested, avd, result, 0, &denied);
if (likely(!audited))
return 0;
/* fall back to ref-walk if we have to generate audit */
if (flags & MAY_NOT_BLOCK)
return -ECHILD;
return slow_avc_audit(state, ssid, tsid, tclass,
requested, audited, denied, result,
a, flags);
a);
}
#define AVC_STRICT 1 /* Ignore permissive mode. */
......@@ -153,6 +155,11 @@ int avc_has_perm(struct selinux_state *state,
u32 ssid, u32 tsid,
u16 tclass, u32 requested,
struct common_audit_data *auditdata);
int avc_has_perm_flags(struct selinux_state *state,
u32 ssid, u32 tsid,
u16 tclass, u32 requested,
struct common_audit_data *auditdata,
int flags);
int avc_has_extended_perms(struct selinux_state *state,
u32 ssid, u32 tsid, u16 tclass, u32 requested,
......
......@@ -246,6 +246,8 @@ struct security_class_mapping secclass_map[] = {
{ COMMON_SOCK_PERMS, NULL } },
{ "perf_event",
{"open", "cpu", "kernel", "tracepoint", "read", "write"} },
{ "lockdown",
{ "integrity", "confidentiality", NULL } },
{ NULL }
};
......
......@@ -14,8 +14,19 @@
#ifndef _SELINUX_IB_PKEY_H
#define _SELINUX_IB_PKEY_H
#ifdef CONFIG_SECURITY_INFINIBAND
void sel_ib_pkey_flush(void);
int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey, u32 *sid);
#else
static inline void sel_ib_pkey_flush(void)
{
return;
}
static inline int sel_ib_pkey_sid(u64 subnet_prefix, u16 pkey, u32 *sid)
{
*sid = SECINITSID_UNLABELED;
return 0;
}
#endif
#endif
......@@ -35,7 +35,7 @@ struct task_security_struct {
u32 create_sid; /* fscreate SID */
u32 keycreate_sid; /* keycreate SID */
u32 sockcreate_sid; /* fscreate SID */
};
} __randomize_layout;
enum label_initialized {
LABEL_INVALID, /* invalid or not initialized */
......
......@@ -69,7 +69,7 @@
struct netlbl_lsm_secattr;
extern int selinux_enabled;
extern int selinux_enabled_boot;
/* Policy capabilities */
enum {
......@@ -99,7 +99,9 @@ struct selinux_avc;
struct selinux_ss;
struct selinux_state {
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
bool disabled;
#endif
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
bool enforcing;
#endif
......@@ -108,22 +110,34 @@ struct selinux_state {
bool policycap[__POLICYDB_CAPABILITY_MAX];
struct selinux_avc *avc;
struct selinux_ss *ss;
};
} __randomize_layout;
void selinux_ss_init(struct selinux_ss **ss);
void selinux_avc_init(struct selinux_avc **avc);
extern struct selinux_state selinux_state;
static inline bool selinux_initialized(const struct selinux_state *state)
{
/* do a synchronized load to avoid race conditions */
return smp_load_acquire(&state->initialized);
}
static inline void selinux_mark_initialized(struct selinux_state *state)
{
/* do a synchronized write to avoid race conditions */
smp_store_release(&state->initialized, true);
}
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
static inline bool enforcing_enabled(struct selinux_state *state)
{
return state->enforcing;
return READ_ONCE(state->enforcing);
}
static inline void enforcing_set(struct selinux_state *state, bool value)
{
state->enforcing = value;
WRITE_ONCE(state->enforcing, value);
}
#else
static inline bool enforcing_enabled(struct selinux_state *state)
......@@ -136,6 +150,23 @@ static inline void enforcing_set(struct selinux_state *state, bool value)
}
#endif
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
static inline bool selinux_disabled(struct selinux_state *state)
{
return READ_ONCE(state->disabled);
}
static inline void selinux_mark_disabled(struct selinux_state *state)
{
WRITE_ONCE(state->disabled, true);
}
#else
static inline bool selinux_disabled(struct selinux_state *state)
{
return false;
}
#endif
static inline bool selinux_policycap_netpeer(void)
{
struct selinux_state *state = &selinux_state;
......@@ -395,5 +426,6 @@ extern int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm);
extern void avtab_cache_init(void);
extern void ebitmap_cache_init(void);
extern void hashtab_cache_init(void);
extern int security_sidtab_hash_stats(struct selinux_state *state, char *page);
#endif /* _SELINUX_SECURITY_H_ */
......@@ -266,7 +266,7 @@ static __init int sel_netif_init(void)
{
int i;
if (!selinux_enabled)
if (!selinux_enabled_boot)
return 0;
for (i = 0; i < SEL_NETIF_HASH_SIZE; i++)
......
......@@ -291,7 +291,7 @@ static __init int sel_netnode_init(void)
{
int iter;
if (!selinux_enabled)
if (!selinux_enabled_boot)
return 0;
for (iter = 0; iter < SEL_NETNODE_HASH_SIZE; iter++) {
......
......@@ -225,7 +225,7 @@ static __init int sel_netport_init(void)
{
int iter;
if (!selinux_enabled)
if (!selinux_enabled_boot)
return 0;
for (iter = 0; iter < SEL_NETPORT_HASH_SIZE; iter++) {
......
......@@ -168,11 +168,10 @@ static ssize_t sel_write_enforce(struct file *file, const char __user *buf,
goto out;
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
"enforcing=%d old_enforcing=%d auid=%u ses=%u"
" enabled=%d old-enabled=%d lsm=selinux res=1",
" enabled=1 old-enabled=1 lsm=selinux res=1",
new_value, old_value,
from_kuid(&init_user_ns, audit_get_loginuid(current)),
audit_get_sessionid(current),
selinux_enabled, selinux_enabled);
audit_get_sessionid(current));
enforcing_set(state, new_value);
if (new_value)
avc_ss_reset(state->avc, 0);
......@@ -282,6 +281,13 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf,
int new_value;
int enforcing;
/* 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");
if (count >= PAGE_SIZE)
return -ENOMEM;
......@@ -304,10 +310,10 @@ static ssize_t sel_write_disable(struct file *file, const char __user *buf,
goto out;
audit_log(audit_context(), GFP_KERNEL, AUDIT_MAC_STATUS,
"enforcing=%d old_enforcing=%d auid=%u ses=%u"
" enabled=%d old-enabled=%d lsm=selinux res=1",
" enabled=0 old-enabled=1 lsm=selinux res=1",
enforcing, enforcing,
from_kuid(&init_user_ns, audit_get_loginuid(current)),
audit_get_sessionid(current), 0, 1);
audit_get_sessionid(current));
}
length = count;
......@@ -1482,6 +1488,32 @@ static ssize_t sel_read_avc_hash_stats(struct file *filp, char __user *buf,
return length;
}
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,
};
static const struct file_operations sel_avc_cache_threshold_ops = {
.read = sel_read_avc_cache_threshold,
.write = sel_write_avc_cache_threshold,
......@@ -1599,6 +1631,37 @@ static int sel_make_avc_files(struct dentry *dir)
return 0;
}
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;
}
static ssize_t sel_read_initcon(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
......@@ -1672,7 +1735,7 @@ static ssize_t sel_read_class(struct file *file, char __user *buf,
{
unsigned long ino = file_inode(file)->i_ino;
char res[TMPBUFLEN];
ssize_t len = snprintf(res, sizeof(res), "%d", sel_ino_to_class(ino));
ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_class(ino));
return simple_read_from_buffer(buf, count, ppos, res, len);
}
......@@ -1686,7 +1749,7 @@ static ssize_t sel_read_perm(struct file *file, char __user *buf,
{
unsigned long ino = file_inode(file)->i_ino;
char res[TMPBUFLEN];
ssize_t len = snprintf(res, sizeof(res), "%d", sel_ino_to_perm(ino));
ssize_t len = scnprintf(res, sizeof(res), "%d", sel_ino_to_perm(ino));
return simple_read_from_buffer(buf, count, ppos, res, len);
}
......@@ -1963,6 +2026,14 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
}
ret = sel_make_avc_files(dentry);
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);
if (ret)
goto err;
......@@ -2040,7 +2111,7 @@ static int __init init_sel_fs(void)
sizeof(NULL_FILE_NAME)-1);
int err;
if (!selinux_enabled)
if (!selinux_enabled_boot)
return 0;
err = sysfs_create_mount_point(fs_kobj, "selinux");
......
......@@ -31,6 +31,7 @@ struct context {
u32 len; /* length of string in bytes */
struct mls_range range;
char *str; /* string representation if context cannot be mapped. */
u32 hash; /* a hash of the string representation */
};
static inline void mls_context_init(struct context *c)
......@@ -168,12 +169,13 @@ static inline int context_cpy(struct context *dst, struct context *src)
kfree(dst->str);
return rc;
}
dst->hash = src->hash;
return 0;
}
static inline void context_destroy(struct context *c)
{
c->user = c->role = c->type = 0;
c->user = c->role = c->type = c->hash = 0;
kfree(c->str);
c->str = NULL;
c->len = 0;
......@@ -182,6 +184,8 @@ static inline void context_destroy(struct context *c)
static inline int context_cmp(struct context *c1, struct context *c2)
{
if (c1->hash && c2->hash && (c1->hash != c2->hash))
return 0;
if (c1->len && c2->len)
return (c1->len == c2->len && !strcmp(c1->str, c2->str));
if (c1->len || c2->len)
......@@ -192,5 +196,10 @@ static inline int context_cmp(struct context *c1, struct context *c2)
mls_context_cmp(c1, c2));
}
static inline unsigned int context_compute_hash(const char *s)
{
return full_name_hash(NULL, s, strlen(s));
}
#endif /* _SS_CONTEXT_H_ */
......@@ -878,6 +878,11 @@ int policydb_load_isids(struct policydb *p, struct sidtab *s)
sidtab_destroy(s);
goto out;
}
rc = context_add_hash(p, &c->context[0]);
if (rc) {
sidtab_destroy(s);
goto out;
}
rc = sidtab_set_initial(s, c->sid[0], &c->context[0]);
if (rc) {
......@@ -2654,7 +2659,7 @@ static int role_trans_write(struct policydb *p, void *fp)
{
struct role_trans *r = p->role_tr;
struct role_trans *tr;
u32 buf[3];
__le32 buf[3];
size_t nel;
int rc;
......@@ -2686,7 +2691,7 @@ static int role_trans_write(struct policydb *p, void *fp)
static int role_allow_write(struct role_allow *r, void *fp)
{
struct role_allow *ra;
u32 buf[2];
__le32 buf[2];
size_t nel;
int rc;
......
......@@ -307,7 +307,7 @@ struct policydb {
u16 process_class;
u32 process_trans_perms;
};
} __randomize_layout;
extern void policydb_destroy(struct policydb *p);
extern int policydb_load_isids(struct policydb *p, struct sidtab *s);
......
......@@ -91,6 +91,12 @@ static int context_struct_to_string(struct policydb *policydb,
char **scontext,
u32 *scontext_len);
static int sidtab_entry_to_string(struct policydb *policydb,
struct sidtab *sidtab,
struct sidtab_entry *entry,
char **scontext,
u32 *scontext_len);
static void context_struct_compute_av(struct policydb *policydb,
struct context *scontext,
struct context *tcontext,
......@@ -716,20 +722,21 @@ static void context_struct_compute_av(struct policydb *policydb,
}
static int security_validtrans_handle_fail(struct selinux_state *state,
struct context *ocontext,
struct context *ncontext,
struct context *tcontext,
struct sidtab_entry *oentry,
struct sidtab_entry *nentry,
struct sidtab_entry *tentry,
u16 tclass)
{
struct policydb *p = &state->ss->policydb;
struct sidtab *sidtab = state->ss->sidtab;
char *o = NULL, *n = NULL, *t = NULL;
u32 olen, nlen, tlen;
if (context_struct_to_string(p, ocontext, &o, &olen))
if (sidtab_entry_to_string(p, sidtab, oentry, &o, &olen))
goto out;
if (context_struct_to_string(p, ncontext, &n, &nlen))
if (sidtab_entry_to_string(p, sidtab, nentry, &n, &nlen))
goto out;
if (context_struct_to_string(p, tcontext, &t, &tlen))
if (sidtab_entry_to_string(p, sidtab, tentry, &t, &tlen))
goto out;
audit_log(audit_context(), GFP_ATOMIC, AUDIT_SELINUX_ERR,
"op=security_validate_transition seresult=denied"
......@@ -751,16 +758,16 @@ static int security_compute_validatetrans(struct selinux_state *state,
{
struct policydb *policydb;
struct sidtab *sidtab;
struct context *ocontext;
struct context *ncontext;
struct context *tcontext;
struct sidtab_entry *oentry;
struct sidtab_entry *nentry;
struct sidtab_entry *tentry;
struct class_datum *tclass_datum;
struct constraint_node *constraint;
u16 tclass;
int rc = 0;
if (!state->initialized)
if (!selinux_initialized(state))
return 0;
read_lock(&state->ss->policy_rwlock);
......@@ -779,24 +786,24 @@ static int security_compute_validatetrans(struct selinux_state *state,
}
tclass_datum = policydb->class_val_to_struct[tclass - 1];
ocontext = sidtab_search(sidtab, oldsid);
if (!ocontext) {
oentry = sidtab_search_entry(sidtab, oldsid);
if (!oentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, oldsid);
rc = -EINVAL;
goto out;
}
ncontext = sidtab_search(sidtab, newsid);
if (!ncontext) {
nentry = sidtab_search_entry(sidtab, newsid);
if (!nentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, newsid);
rc = -EINVAL;
goto out;
}
tcontext = sidtab_search(sidtab, tasksid);
if (!tcontext) {
tentry = sidtab_search_entry(sidtab, tasksid);
if (!tentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, tasksid);
rc = -EINVAL;
......@@ -805,15 +812,16 @@ static int security_compute_validatetrans(struct selinux_state *state,
constraint = tclass_datum->validatetrans;
while (constraint) {
if (!constraint_expr_eval(policydb, ocontext, ncontext,
tcontext, constraint->expr)) {
if (!constraint_expr_eval(policydb, &oentry->context,
&nentry->context, &tentry->context,
constraint->expr)) {
if (user)
rc = -EPERM;
else
rc = security_validtrans_handle_fail(state,
ocontext,
ncontext,
tcontext,
oentry,
nentry,
tentry,
tclass);
goto out;
}
......@@ -855,12 +863,12 @@ int security_bounded_transition(struct selinux_state *state,
{
struct policydb *policydb;
struct sidtab *sidtab;
struct context *old_context, *new_context;
struct sidtab_entry *old_entry, *new_entry;
struct type_datum *type;
int index;
int rc;
if (!state->initialized)
if (!selinux_initialized(state))
return 0;
read_lock(&state->ss->policy_rwlock);
......@@ -869,16 +877,16 @@ int security_bounded_transition(struct selinux_state *state,
sidtab = state->ss->sidtab;
rc = -EINVAL;
old_context = sidtab_search(sidtab, old_sid);
if (!old_context) {
old_entry = sidtab_search_entry(sidtab, old_sid);
if (!old_entry) {
pr_err("SELinux: %s: unrecognized SID %u\n",
__func__, old_sid);
goto out;
}
rc = -EINVAL;
new_context = sidtab_search(sidtab, new_sid);
if (!new_context) {
new_entry = sidtab_search_entry(sidtab, new_sid);
if (!new_entry) {
pr_err("SELinux: %s: unrecognized SID %u\n",
__func__, new_sid);
goto out;
......@@ -886,10 +894,10 @@ int security_bounded_transition(struct selinux_state *state,
rc = 0;
/* type/domain unchanged */
if (old_context->type == new_context->type)
if (old_entry->context.type == new_entry->context.type)
goto out;
index = new_context->type;
index = new_entry->context.type;
while (true) {
type = policydb->type_val_to_struct[index - 1];
BUG_ON(!type);
......@@ -901,7 +909,7 @@ int security_bounded_transition(struct selinux_state *state,
/* @newsid is bounded by @oldsid */
rc = 0;
if (type->bounds == old_context->type)
if (type->bounds == old_entry->context.type)
break;
index = type->bounds;
......@@ -912,10 +920,10 @@ int security_bounded_transition(struct selinux_state *state,
char *new_name = NULL;
u32 length;
if (!context_struct_to_string(policydb, old_context,
&old_name, &length) &&
!context_struct_to_string(policydb, new_context,
&new_name, &length)) {
if (!sidtab_entry_to_string(policydb, sidtab, old_entry,
&old_name, &length) &&
!sidtab_entry_to_string(policydb, sidtab, new_entry,
&new_name, &length)) {
audit_log(audit_context(),
GFP_ATOMIC, AUDIT_SELINUX_ERR,
"op=security_bounded_transition "
......@@ -1019,7 +1027,7 @@ void security_compute_xperms_decision(struct selinux_state *state,
memset(xpermd->dontaudit->p, 0, sizeof(xpermd->dontaudit->p));
read_lock(&state->ss->policy_rwlock);
if (!state->initialized)
if (!selinux_initialized(state))
goto allow;
policydb = &state->ss->policydb;
......@@ -1104,7 +1112,7 @@ void security_compute_av(struct selinux_state *state,
read_lock(&state->ss->policy_rwlock);
avd_init(state, avd);
xperms->len = 0;
if (!state->initialized)
if (!selinux_initialized(state))
goto allow;
policydb = &state->ss->policydb;
......@@ -1158,7 +1166,7 @@ void security_compute_av_user(struct selinux_state *state,
read_lock(&state->ss->policy_rwlock);
avd_init(state, avd);
if (!state->initialized)
if (!selinux_initialized(state))
goto allow;
policydb = &state->ss->policydb;
......@@ -1255,8 +1263,42 @@ static int context_struct_to_string(struct policydb *p,
return 0;
}
static int sidtab_entry_to_string(struct policydb *p,
struct sidtab *sidtab,
struct sidtab_entry *entry,
char **scontext, u32 *scontext_len)
{
int rc = sidtab_sid2str_get(sidtab, entry, scontext, scontext_len);
if (rc != -ENOENT)
return rc;
rc = context_struct_to_string(p, &entry->context, scontext,
scontext_len);
if (!rc && scontext)
sidtab_sid2str_put(sidtab, entry, *scontext, *scontext_len);
return rc;
}
#include "initial_sid_to_string.h"
int security_sidtab_hash_stats(struct selinux_state *state, char *page)
{
int rc;
if (!selinux_initialized(state)) {
pr_err("SELinux: %s: called before initial load_policy\n",
__func__);
return -EINVAL;
}
read_lock(&state->ss->policy_rwlock);
rc = sidtab_hash_stats(state->ss->sidtab, page);
read_unlock(&state->ss->policy_rwlock);
return rc;
}
const char *security_get_initial_sid_context(u32 sid)
{
if (unlikely(sid > SECINITSID_NUM))
......@@ -1271,14 +1313,14 @@ static int security_sid_to_context_core(struct selinux_state *state,
{
struct policydb *policydb;
struct sidtab *sidtab;
struct context *context;
struct sidtab_entry *entry;
int rc = 0;
if (scontext)
*scontext = NULL;
*scontext_len = 0;
if (!state->initialized) {
if (!selinux_initialized(state)) {
if (sid <= SECINITSID_NUM) {
char *scontextp;
......@@ -1302,21 +1344,23 @@ static int security_sid_to_context_core(struct selinux_state *state,
read_lock(&state->ss->policy_rwlock);
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;
if (force)
context = sidtab_search_force(sidtab, sid);
entry = sidtab_search_entry_force(sidtab, sid);
else
context = sidtab_search(sidtab, sid);
if (!context) {
entry = sidtab_search_entry(sidtab, sid);
if (!entry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, sid);
rc = -EINVAL;
goto out_unlock;
}
if (only_invalid && !context->len)
rc = 0;
else
rc = context_struct_to_string(policydb, context, scontext,
scontext_len);
if (only_invalid && !entry->context.len)
goto out_unlock;
rc = sidtab_entry_to_string(policydb, sidtab, entry, scontext,
scontext_len);
out_unlock:
read_unlock(&state->ss->policy_rwlock);
out:
......@@ -1449,6 +1493,42 @@ static int string_to_context_struct(struct policydb *pol,
return rc;
}
int context_add_hash(struct policydb *policydb,
struct context *context)
{
int rc;
char *str;
int len;
if (context->str) {
context->hash = context_compute_hash(context->str);
} else {
rc = context_struct_to_string(policydb, context,
&str, &len);
if (rc)
return rc;
context->hash = context_compute_hash(str);
kfree(str);
}
return 0;
}
static int context_struct_to_sid(struct selinux_state *state,
struct context *context, u32 *sid)
{
int rc;
struct sidtab *sidtab = state->ss->sidtab;
struct policydb *policydb = &state->ss->policydb;
if (!context->hash) {
rc = context_add_hash(policydb, context);
if (rc)
return rc;
}
return sidtab_context_to_sid(sidtab, context, sid);
}
static int security_context_to_sid_core(struct selinux_state *state,
const char *scontext, u32 scontext_len,
u32 *sid, u32 def_sid, gfp_t gfp_flags,
......@@ -1469,7 +1549,7 @@ static int security_context_to_sid_core(struct selinux_state *state,
if (!scontext2)
return -ENOMEM;
if (!state->initialized) {
if (!selinux_initialized(state)) {
int i;
for (i = 1; i < SECINITSID_NUM; i++) {
......@@ -1501,7 +1581,7 @@ static int security_context_to_sid_core(struct selinux_state *state,
str = NULL;
} else if (rc)
goto out_unlock;
rc = sidtab_context_to_sid(sidtab, &context, sid);
rc = context_struct_to_sid(state, &context, sid);
context_destroy(&context);
out_unlock:
read_unlock(&state->ss->policy_rwlock);
......@@ -1574,19 +1654,20 @@ int security_context_to_sid_force(struct selinux_state *state,
static int compute_sid_handle_invalid_context(
struct selinux_state *state,
struct context *scontext,
struct context *tcontext,
struct sidtab_entry *sentry,
struct sidtab_entry *tentry,
u16 tclass,
struct context *newcontext)
{
struct policydb *policydb = &state->ss->policydb;
struct sidtab *sidtab = state->ss->sidtab;
char *s = NULL, *t = NULL, *n = NULL;
u32 slen, tlen, nlen;
struct audit_buffer *ab;
if (context_struct_to_string(policydb, scontext, &s, &slen))
if (sidtab_entry_to_string(policydb, sidtab, sentry, &s, &slen))
goto out;
if (context_struct_to_string(policydb, tcontext, &t, &tlen))
if (sidtab_entry_to_string(policydb, sidtab, tentry, &t, &tlen))
goto out;
if (context_struct_to_string(policydb, newcontext, &n, &nlen))
goto out;
......@@ -1645,7 +1726,8 @@ static int security_compute_sid(struct selinux_state *state,
struct policydb *policydb;
struct sidtab *sidtab;
struct class_datum *cladatum = NULL;
struct context *scontext = NULL, *tcontext = NULL, newcontext;
struct context *scontext, *tcontext, newcontext;
struct sidtab_entry *sentry, *tentry;
struct role_trans *roletr = NULL;
struct avtab_key avkey;
struct avtab_datum *avdatum;
......@@ -1654,7 +1736,7 @@ static int security_compute_sid(struct selinux_state *state,
int rc = 0;
bool sock;
if (!state->initialized) {
if (!selinux_initialized(state)) {
switch (orig_tclass) {
case SECCLASS_PROCESS: /* kernel value */
*out_sid = ssid;
......@@ -1682,21 +1764,24 @@ static int security_compute_sid(struct selinux_state *state,
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;
scontext = sidtab_search(sidtab, ssid);
if (!scontext) {
sentry = sidtab_search_entry(sidtab, ssid);
if (!sentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, ssid);
rc = -EINVAL;
goto out_unlock;
}
tcontext = sidtab_search(sidtab, tsid);
if (!tcontext) {
tentry = sidtab_search_entry(sidtab, tsid);
if (!tentry) {
pr_err("SELinux: %s: unrecognized SID %d\n",
__func__, tsid);
rc = -EINVAL;
goto out_unlock;
}
scontext = &sentry->context;
tcontext = &tentry->context;
if (tclass && tclass <= policydb->p_classes.nprim)
cladatum = policydb->class_val_to_struct[tclass - 1];
......@@ -1797,15 +1882,13 @@ static int security_compute_sid(struct selinux_state *state,
/* Check the validity of the context. */
if (!policydb_context_isvalid(policydb, &newcontext)) {
rc = compute_sid_handle_invalid_context(state, scontext,
tcontext,
tclass,
&newcontext);
rc = compute_sid_handle_invalid_context(state, sentry, tentry,
tclass, &newcontext);
if (rc)
goto out_unlock;
}
/* Obtain the sid for the context. */
rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid);
rc = context_struct_to_sid(state, &newcontext, out_sid);
out_unlock:
read_unlock(&state->ss->policy_rwlock);
context_destroy(&newcontext);
......@@ -1957,6 +2040,7 @@ static int convert_context(struct context *oldc, struct context *newc, void *p)
context_init(newc);
newc->str = s;
newc->len = oldc->len;
newc->hash = oldc->hash;
return 0;
}
kfree(s);
......@@ -2033,6 +2117,10 @@ static int convert_context(struct context *oldc, struct context *newc, void *p)
goto bad;
}
rc = context_add_hash(args->newp, newc);
if (rc)
goto bad;
return 0;
bad:
/* Map old representation to string and save it. */
......@@ -2042,6 +2130,7 @@ static int convert_context(struct context *oldc, struct context *newc, void *p)
context_destroy(newc);
newc->str = s;
newc->len = len;
newc->hash = context_compute_hash(s);
pr_info("SELinux: Context %s became invalid (unmapped).\n",
newc->str);
return 0;
......@@ -2094,26 +2183,17 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len)
int rc = 0;
struct policy_file file = { data, len }, *fp = &file;
oldpolicydb = kcalloc(2, sizeof(*oldpolicydb), GFP_KERNEL);
if (!oldpolicydb) {
rc = -ENOMEM;
goto out;
}
newpolicydb = oldpolicydb + 1;
policydb = &state->ss->policydb;
newsidtab = kmalloc(sizeof(*newsidtab), GFP_KERNEL);
if (!newsidtab) {
rc = -ENOMEM;
goto out;
}
if (!newsidtab)
return -ENOMEM;
if (!state->initialized) {
if (!selinux_initialized(state)) {
rc = policydb_read(policydb, fp);
if (rc) {
kfree(newsidtab);
goto out;
return rc;
}
policydb->len = len;
......@@ -2122,19 +2202,19 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len)
if (rc) {
kfree(newsidtab);
policydb_destroy(policydb);
goto out;
return rc;
}
rc = policydb_load_isids(policydb, newsidtab);
if (rc) {
kfree(newsidtab);
policydb_destroy(policydb);
goto out;
return rc;
}
state->ss->sidtab = newsidtab;
security_load_policycaps(state);
state->initialized = 1;
selinux_mark_initialized(state);
seqno = ++state->ss->latest_granting;
selinux_complete_init();
avc_ss_reset(state->avc, seqno);
......@@ -2142,9 +2222,16 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len)
selinux_status_update_policyload(state, seqno);
selinux_netlbl_cache_invalidate();
selinux_xfrm_notify_policyload();
goto out;
return 0;
}
oldpolicydb = kcalloc(2, sizeof(*oldpolicydb), GFP_KERNEL);
if (!oldpolicydb) {
kfree(newsidtab);
return -ENOMEM;
}
newpolicydb = oldpolicydb + 1;
rc = policydb_read(newpolicydb, fp);
if (rc) {
kfree(newsidtab);
......@@ -2260,14 +2347,12 @@ int security_port_sid(struct selinux_state *state,
u8 protocol, u16 port, u32 *out_sid)
{
struct policydb *policydb;
struct sidtab *sidtab;
struct ocontext *c;
int rc = 0;
read_lock(&state->ss->policy_rwlock);
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;
c = policydb->ocontexts[OCON_PORT];
while (c) {
......@@ -2280,8 +2365,7 @@ int security_port_sid(struct selinux_state *state,
if (c) {
if (!c->sid[0]) {
rc = sidtab_context_to_sid(sidtab,
&c->context[0],
rc = context_struct_to_sid(state, &c->context[0],
&c->sid[0]);
if (rc)
goto out;
......@@ -2306,14 +2390,12 @@ int security_ib_pkey_sid(struct selinux_state *state,
u64 subnet_prefix, u16 pkey_num, u32 *out_sid)
{
struct policydb *policydb;
struct sidtab *sidtab;
struct ocontext *c;
int rc = 0;
read_lock(&state->ss->policy_rwlock);
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;
c = policydb->ocontexts[OCON_IBPKEY];
while (c) {
......@@ -2327,7 +2409,7 @@ int security_ib_pkey_sid(struct selinux_state *state,
if (c) {
if (!c->sid[0]) {
rc = sidtab_context_to_sid(sidtab,
rc = context_struct_to_sid(state,
&c->context[0],
&c->sid[0]);
if (rc)
......@@ -2352,14 +2434,12 @@ int security_ib_endport_sid(struct selinux_state *state,
const char *dev_name, u8 port_num, u32 *out_sid)
{
struct policydb *policydb;
struct sidtab *sidtab;
struct ocontext *c;
int rc = 0;
read_lock(&state->ss->policy_rwlock);
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;
c = policydb->ocontexts[OCON_IBENDPORT];
while (c) {
......@@ -2374,8 +2454,7 @@ int security_ib_endport_sid(struct selinux_state *state,
if (c) {
if (!c->sid[0]) {
rc = sidtab_context_to_sid(sidtab,
&c->context[0],
rc = context_struct_to_sid(state, &c->context[0],
&c->sid[0]);
if (rc)
goto out;
......@@ -2398,14 +2477,12 @@ int security_netif_sid(struct selinux_state *state,
char *name, u32 *if_sid)
{
struct policydb *policydb;
struct sidtab *sidtab;
int rc = 0;
struct ocontext *c;
read_lock(&state->ss->policy_rwlock);
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;
c = policydb->ocontexts[OCON_NETIF];
while (c) {
......@@ -2416,13 +2493,11 @@ int security_netif_sid(struct selinux_state *state,
if (c) {
if (!c->sid[0] || !c->sid[1]) {
rc = sidtab_context_to_sid(sidtab,
&c->context[0],
&c->sid[0]);
rc = context_struct_to_sid(state, &c->context[0],
&c->sid[0]);
if (rc)
goto out;
rc = sidtab_context_to_sid(sidtab,
&c->context[1],
rc = context_struct_to_sid(state, &c->context[1],
&c->sid[1]);
if (rc)
goto out;
......@@ -2463,14 +2538,12 @@ int security_node_sid(struct selinux_state *state,
u32 *out_sid)
{
struct policydb *policydb;
struct sidtab *sidtab;
int rc;
struct ocontext *c;
read_lock(&state->ss->policy_rwlock);
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;
switch (domain) {
case AF_INET: {
......@@ -2512,7 +2585,7 @@ int security_node_sid(struct selinux_state *state,
if (c) {
if (!c->sid[0]) {
rc = sidtab_context_to_sid(sidtab,
rc = context_struct_to_sid(state,
&c->context[0],
&c->sid[0]);
if (rc)
......@@ -2564,7 +2637,7 @@ int security_get_user_sids(struct selinux_state *state,
*sids = NULL;
*nel = 0;
if (!state->initialized)
if (!selinux_initialized(state))
goto out;
read_lock(&state->ss->policy_rwlock);
......@@ -2596,12 +2669,17 @@ int security_get_user_sids(struct selinux_state *state,
usercon.role = i + 1;
ebitmap_for_each_positive_bit(&role->types, tnode, j) {
usercon.type = j + 1;
/*
* The same context struct is reused here so the hash
* must be reset.
*/
usercon.hash = 0;
if (mls_setup_user_range(policydb, fromcon, user,
&usercon))
continue;
rc = sidtab_context_to_sid(sidtab, &usercon, &sid);
rc = context_struct_to_sid(state, &usercon, &sid);
if (rc)
goto out_unlock;
if (mynel < maxnel) {
......@@ -2672,7 +2750,6 @@ static inline int __security_genfs_sid(struct selinux_state *state,
u32 *sid)
{
struct policydb *policydb = &state->ss->policydb;
struct sidtab *sidtab = state->ss->sidtab;
int len;
u16 sclass;
struct genfs *genfs;
......@@ -2707,7 +2784,7 @@ static inline int __security_genfs_sid(struct selinux_state *state,
goto out;
if (!c->sid[0]) {
rc = sidtab_context_to_sid(sidtab, &c->context[0], &c->sid[0]);
rc = context_struct_to_sid(state, &c->context[0], &c->sid[0]);
if (rc)
goto out;
}
......@@ -2749,7 +2826,6 @@ int security_genfs_sid(struct selinux_state *state,
int security_fs_use(struct selinux_state *state, struct super_block *sb)
{
struct policydb *policydb;
struct sidtab *sidtab;
int rc = 0;
struct ocontext *c;
struct superblock_security_struct *sbsec = sb->s_security;
......@@ -2758,7 +2834,6 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
read_lock(&state->ss->policy_rwlock);
policydb = &state->ss->policydb;
sidtab = state->ss->sidtab;
c = policydb->ocontexts[OCON_FSUSE];
while (c) {
......@@ -2770,7 +2845,7 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
if (c) {
sbsec->behavior = c->v.behavior;
if (!c->sid[0]) {
rc = sidtab_context_to_sid(sidtab, &c->context[0],
rc = context_struct_to_sid(state, &c->context[0],
&c->sid[0]);
if (rc)
goto out;
......@@ -2798,7 +2873,7 @@ int security_get_bools(struct selinux_state *state,
struct policydb *policydb;
int i, rc;
if (!state->initialized) {
if (!selinux_initialized(state)) {
*len = 0;
*names = NULL;
*values = NULL;
......@@ -2973,7 +3048,7 @@ int security_sid_mls_copy(struct selinux_state *state,
int rc;
rc = 0;
if (!state->initialized || !policydb->mls_enabled) {
if (!selinux_initialized(state) || !policydb->mls_enabled) {
*new_sid = sid;
goto out;
}
......@@ -3026,8 +3101,7 @@ int security_sid_mls_copy(struct selinux_state *state,
goto out_unlock;
}
}
rc = sidtab_context_to_sid(sidtab, &newcon, new_sid);
rc = context_struct_to_sid(state, &newcon, new_sid);
out_unlock:
read_unlock(&state->ss->policy_rwlock);
context_destroy(&newcon);
......@@ -3141,7 +3215,7 @@ int security_get_classes(struct selinux_state *state,
struct policydb *policydb = &state->ss->policydb;
int rc;
if (!state->initialized) {
if (!selinux_initialized(state)) {
*nclasses = 0;
*classes = NULL;
return 0;
......@@ -3290,7 +3364,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule)
*rule = NULL;
if (!state->initialized)
if (!selinux_initialized(state))
return -EOPNOTSUPP;
switch (field) {
......@@ -3589,7 +3663,7 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state,
struct context *ctx;
struct context ctx_new;
if (!state->initialized) {
if (!selinux_initialized(state)) {
*sid = SECSID_NULL;
return 0;
}
......@@ -3620,7 +3694,7 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state,
if (!mls_context_isvalid(policydb, &ctx_new))
goto out_free;
rc = sidtab_context_to_sid(sidtab, &ctx_new, sid);
rc = context_struct_to_sid(state, &ctx_new, sid);
if (rc)
goto out_free;
......@@ -3656,7 +3730,7 @@ int security_netlbl_sid_to_secattr(struct selinux_state *state,
int rc;
struct context *ctx;
if (!state->initialized)
if (!selinux_initialized(state))
return 0;
read_lock(&state->ss->policy_rwlock);
......@@ -3695,7 +3769,7 @@ int security_read_policy(struct selinux_state *state,
int rc;
struct policy_file fp;
if (!state->initialized)
if (!selinux_initialized(state))
return -EINVAL;
*len = security_policydb_len(state);
......
......@@ -8,7 +8,7 @@
#define _SS_SERVICES_H_
#include "policydb.h"
#include "sidtab.h"
#include "context.h"
/* Mapping for a single class */
struct selinux_mapping {
......@@ -31,7 +31,7 @@ struct selinux_ss {
struct selinux_map map;
struct page *status_page;
struct mutex status_lock;
};
} __randomize_layout;
void services_compute_xperms_drivers(struct extended_perms *xperms,
struct avtab_node *node);
......@@ -39,4 +39,6 @@ void services_compute_xperms_drivers(struct extended_perms *xperms,
void services_compute_xperms_decision(struct extended_perms_decision *xpermd,
struct avtab_node *node);
int context_add_hash(struct policydb *policydb, struct context *context);
#endif /* _SS_SERVICES_H_ */
......@@ -9,6 +9,8 @@
*/
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/rcupdate.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/spinlock.h>
......@@ -17,44 +19,125 @@
#include "security.h"
#include "sidtab.h"
struct sidtab_str_cache {
struct rcu_head rcu_member;
struct list_head lru_member;
struct sidtab_entry *parent;
u32 len;
char str[];
};
#define index_to_sid(index) (index + SECINITSID_NUM + 1)
#define sid_to_index(sid) (sid - (SECINITSID_NUM + 1))
int sidtab_init(struct sidtab *s)
{
u32 i;
memset(s->roots, 0, sizeof(s->roots));
/* max count is SIDTAB_MAX so valid index is always < SIDTAB_MAX */
for (i = 0; i < SIDTAB_RCACHE_SIZE; i++)
s->rcache[i] = SIDTAB_MAX;
for (i = 0; i < SECINITSID_NUM; i++)
s->isids[i].set = 0;
s->count = 0;
s->convert = NULL;
hash_init(s->context_to_sid);
spin_lock_init(&s->lock);
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
s->cache_free_slots = CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE;
INIT_LIST_HEAD(&s->cache_lru_list);
spin_lock_init(&s->cache_lock);
#endif
return 0;
}
static u32 context_to_sid(struct sidtab *s, struct context *context)
{
struct sidtab_entry *entry;
u32 sid = 0;
rcu_read_lock();
hash_for_each_possible_rcu(s->context_to_sid, entry, list,
context->hash) {
if (context_cmp(&entry->context, context)) {
sid = entry->sid;
break;
}
}
rcu_read_unlock();
return sid;
}
int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context)
{
struct sidtab_isid_entry *entry;
struct sidtab_isid_entry *isid;
int rc;
if (sid == 0 || sid > SECINITSID_NUM)
return -EINVAL;
entry = &s->isids[sid - 1];
isid = &s->isids[sid - 1];
rc = context_cpy(&entry->context, context);
rc = context_cpy(&isid->entry.context, context);
if (rc)
return rc;
entry->set = 1;
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
isid->entry.cache = NULL;
#endif
isid->set = 1;
/*
* Multiple initial sids may map to the same context. Check that this
* context is not already represented in the context_to_sid hashtable
* to avoid duplicate entries and long linked lists upon hash
* collision.
*/
if (!context_to_sid(s, context)) {
isid->entry.sid = sid;
hash_add(s->context_to_sid, &isid->entry.list, context->hash);
}
return 0;
}
int sidtab_hash_stats(struct sidtab *sidtab, char *page)
{
int i;
int chain_len = 0;
int slots_used = 0;
int entries = 0;
int max_chain_len = 0;
int cur_bucket = 0;
struct sidtab_entry *entry;
rcu_read_lock();
hash_for_each_rcu(sidtab->context_to_sid, i, entry, list) {
entries++;
if (i == cur_bucket) {
chain_len++;
if (chain_len == 1)
slots_used++;
} else {
cur_bucket = i;
if (chain_len > max_chain_len)
max_chain_len = chain_len;
chain_len = 0;
}
}
rcu_read_unlock();
if (chain_len > max_chain_len)
max_chain_len = chain_len;
return scnprintf(page, PAGE_SIZE, "entries: %d\nbuckets used: %d/%d\n"
"longest chain: %d\n", entries,
slots_used, SIDTAB_HASH_BUCKETS, max_chain_len);
}
static u32 sidtab_level_from_count(u32 count)
{
u32 capacity = SIDTAB_LEAF_ENTRIES;
......@@ -88,7 +171,8 @@ static int sidtab_alloc_roots(struct sidtab *s, u32 level)
return 0;
}
static struct context *sidtab_do_lookup(struct sidtab *s, u32 index, int alloc)
static struct sidtab_entry *sidtab_do_lookup(struct sidtab *s, u32 index,
int alloc)
{
union sidtab_entry_inner *entry;
u32 level, capacity_shift, leaf_index = index / SIDTAB_LEAF_ENTRIES;
......@@ -125,10 +209,10 @@ static struct context *sidtab_do_lookup(struct sidtab *s, u32 index, int alloc)
if (!entry->ptr_leaf)
return NULL;
}
return &entry->ptr_leaf->entries[index % SIDTAB_LEAF_ENTRIES].context;
return &entry->ptr_leaf->entries[index % SIDTAB_LEAF_ENTRIES];
}
static struct context *sidtab_lookup(struct sidtab *s, u32 index)
static struct sidtab_entry *sidtab_lookup(struct sidtab *s, u32 index)
{
/* read entries only after reading count */
u32 count = smp_load_acquire(&s->count);
......@@ -139,148 +223,62 @@ static struct context *sidtab_lookup(struct sidtab *s, u32 index)
return sidtab_do_lookup(s, index, 0);
}
static struct context *sidtab_lookup_initial(struct sidtab *s, u32 sid)
static struct sidtab_entry *sidtab_lookup_initial(struct sidtab *s, u32 sid)
{
return s->isids[sid - 1].set ? &s->isids[sid - 1].context : NULL;
return s->isids[sid - 1].set ? &s->isids[sid - 1].entry : NULL;
}
static struct context *sidtab_search_core(struct sidtab *s, u32 sid, int force)
static struct sidtab_entry *sidtab_search_core(struct sidtab *s, u32 sid,
int force)
{
struct context *context;
if (sid != 0) {
struct sidtab_entry *entry;
if (sid > SECINITSID_NUM)
context = sidtab_lookup(s, sid - (SECINITSID_NUM + 1));
entry = sidtab_lookup(s, sid_to_index(sid));
else
context = sidtab_lookup_initial(s, sid);
if (context && (!context->len || force))
return context;
entry = sidtab_lookup_initial(s, sid);
if (entry && (!entry->context.len || force))
return entry;
}
return sidtab_lookup_initial(s, SECINITSID_UNLABELED);
}
struct context *sidtab_search(struct sidtab *s, u32 sid)
struct sidtab_entry *sidtab_search_entry(struct sidtab *s, u32 sid)
{
return sidtab_search_core(s, sid, 0);
}
struct context *sidtab_search_force(struct sidtab *s, u32 sid)
struct sidtab_entry *sidtab_search_entry_force(struct sidtab *s, u32 sid)
{
return sidtab_search_core(s, sid, 1);
}
static int sidtab_find_context(union sidtab_entry_inner entry,
u32 *pos, u32 count, u32 level,
struct context *context, u32 *index)
{
int rc;
u32 i;
if (level != 0) {
struct sidtab_node_inner *node = entry.ptr_inner;
i = 0;
while (i < SIDTAB_INNER_ENTRIES && *pos < count) {
rc = sidtab_find_context(node->entries[i],
pos, count, level - 1,
context, index);
if (rc == 0)
return 0;
i++;
}
} else {
struct sidtab_node_leaf *node = entry.ptr_leaf;
i = 0;
while (i < SIDTAB_LEAF_ENTRIES && *pos < count) {
if (context_cmp(&node->entries[i].context, context)) {
*index = *pos;
return 0;
}
(*pos)++;
i++;
}
}
return -ENOENT;
}
static void sidtab_rcache_update(struct sidtab *s, u32 index, u32 pos)
{
while (pos > 0) {
WRITE_ONCE(s->rcache[pos], READ_ONCE(s->rcache[pos - 1]));
--pos;
}
WRITE_ONCE(s->rcache[0], index);
}
static void sidtab_rcache_push(struct sidtab *s, u32 index)
{
sidtab_rcache_update(s, index, SIDTAB_RCACHE_SIZE - 1);
}
static int sidtab_rcache_search(struct sidtab *s, struct context *context,
u32 *index)
{
u32 i;
for (i = 0; i < SIDTAB_RCACHE_SIZE; i++) {
u32 v = READ_ONCE(s->rcache[i]);
if (v >= SIDTAB_MAX)
continue;
if (context_cmp(sidtab_do_lookup(s, v, 0), context)) {
sidtab_rcache_update(s, v, i);
*index = v;
return 0;
}
}
return -ENOENT;
}
static int sidtab_reverse_lookup(struct sidtab *s, struct context *context,
u32 *index)
int sidtab_context_to_sid(struct sidtab *s, struct context *context,
u32 *sid)
{
unsigned long flags;
u32 count, count_locked, level, pos;
u32 count;
struct sidtab_convert_params *convert;
struct context *dst, *dst_convert;
struct sidtab_entry *dst, *dst_convert;
int rc;
rc = sidtab_rcache_search(s, context, index);
if (rc == 0)
return 0;
/* read entries only after reading count */
count = smp_load_acquire(&s->count);
level = sidtab_level_from_count(count);
pos = 0;
rc = sidtab_find_context(s->roots[level], &pos, count, level,
context, index);
if (rc == 0) {
sidtab_rcache_push(s, *index);
*sid = context_to_sid(s, context);
if (*sid)
return 0;
}
/* lock-free search failed: lock, re-search, and insert if not found */
spin_lock_irqsave(&s->lock, flags);
rc = 0;
*sid = context_to_sid(s, context);
if (*sid)
goto out_unlock;
/* read entries only after reading count */
count = smp_load_acquire(&s->count);
convert = s->convert;
count_locked = s->count;
level = sidtab_level_from_count(count_locked);
/* if count has changed before we acquired the lock, then catch up */
while (count < count_locked) {
if (context_cmp(sidtab_do_lookup(s, count, 0), context)) {
sidtab_rcache_push(s, count);
*index = count;
rc = 0;
goto out_unlock;
}
++count;
}
/* bail out if we already reached max entries */
rc = -EOVERFLOW;
......@@ -293,7 +291,9 @@ static int sidtab_reverse_lookup(struct sidtab *s, struct context *context,
if (!dst)
goto out_unlock;
rc = context_cpy(dst, context);
dst->sid = index_to_sid(count);
rc = context_cpy(&dst->context, context);
if (rc)
goto out_unlock;
......@@ -305,29 +305,32 @@ static int sidtab_reverse_lookup(struct sidtab *s, struct context *context,
rc = -ENOMEM;
dst_convert = sidtab_do_lookup(convert->target, count, 1);
if (!dst_convert) {
context_destroy(dst);
context_destroy(&dst->context);
goto out_unlock;
}
rc = convert->func(context, dst_convert, convert->args);
rc = convert->func(context, &dst_convert->context,
convert->args);
if (rc) {
context_destroy(dst);
context_destroy(&dst->context);
goto out_unlock;
}
/* at this point we know the insert won't fail */
dst_convert->sid = index_to_sid(count);
convert->target->count = count + 1;
hash_add_rcu(convert->target->context_to_sid,
&dst_convert->list, dst_convert->context.hash);
}
if (context->len)
pr_info("SELinux: Context %s is not valid (left unmapped).\n",
context->str);
sidtab_rcache_push(s, count);
*index = count;
*sid = index_to_sid(count);
/* write entries before writing new count */
/* write entries before updating count */
smp_store_release(&s->count, count + 1);
hash_add_rcu(s->context_to_sid, &dst->list, dst->context.hash);
rc = 0;
out_unlock:
......@@ -335,25 +338,19 @@ static int sidtab_reverse_lookup(struct sidtab *s, struct context *context,
return rc;
}
int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid)
static void sidtab_convert_hashtable(struct sidtab *s, u32 count)
{
int rc;
struct sidtab_entry *entry;
u32 i;
for (i = 0; i < SECINITSID_NUM; i++) {
struct sidtab_isid_entry *entry = &s->isids[i];
for (i = 0; i < count; i++) {
entry = sidtab_do_lookup(s, i, 0);
entry->sid = index_to_sid(i);
if (entry->set && context_cmp(context, &entry->context)) {
*sid = i + 1;
return 0;
}
}
hash_add_rcu(s->context_to_sid, &entry->list,
entry->context.hash);
rc = sidtab_reverse_lookup(s, context, sid);
if (rc)
return rc;
*sid += SECINITSID_NUM + 1;
return 0;
}
}
static int sidtab_convert_tree(union sidtab_entry_inner *edst,
......@@ -435,7 +432,7 @@ int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params)
/* enable live convert of new entries */
s->convert = params;
/* we can safely do the rest of the conversion outside the lock */
/* we can safely convert the tree outside the lock */
spin_unlock_irqrestore(&s->lock, flags);
pr_info("SELinux: Converting %u SID table entries...\n", count);
......@@ -449,8 +446,25 @@ int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params)
spin_lock_irqsave(&s->lock, flags);
s->convert = NULL;
spin_unlock_irqrestore(&s->lock, flags);
return rc;
}
return rc;
/*
* The hashtable can also be modified in sidtab_context_to_sid()
* so we must re-acquire the lock here.
*/
spin_lock_irqsave(&s->lock, flags);
sidtab_convert_hashtable(params->target, count);
spin_unlock_irqrestore(&s->lock, flags);
return 0;
}
static void sidtab_destroy_entry(struct sidtab_entry *entry)
{
context_destroy(&entry->context);
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
kfree(rcu_dereference_raw(entry->cache));
#endif
}
static void sidtab_destroy_tree(union sidtab_entry_inner entry, u32 level)
......@@ -473,7 +487,7 @@ static void sidtab_destroy_tree(union sidtab_entry_inner entry, u32 level)
return;
for (i = 0; i < SIDTAB_LEAF_ENTRIES; i++)
context_destroy(&node->entries[i].context);
sidtab_destroy_entry(&node->entries[i]);
kfree(node);
}
}
......@@ -484,11 +498,101 @@ void sidtab_destroy(struct sidtab *s)
for (i = 0; i < SECINITSID_NUM; i++)
if (s->isids[i].set)
context_destroy(&s->isids[i].context);
sidtab_destroy_entry(&s->isids[i].entry);
level = SIDTAB_MAX_LEVEL;
while (level && !s->roots[level].ptr_inner)
--level;
sidtab_destroy_tree(s->roots[level], level);
/*
* The context_to_sid hashtable's objects are all shared
* with the isids array and context tree, and so don't need
* to be cleaned up here.
*/
}
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry,
const char *str, u32 str_len)
{
struct sidtab_str_cache *cache, *victim = NULL;
/* do not cache invalid contexts */
if (entry->context.len)
return;
/*
* Skip the put operation when in non-task context to avoid the need
* to disable interrupts while holding s->cache_lock.
*/
if (!in_task())
return;
spin_lock(&s->cache_lock);
cache = rcu_dereference_protected(entry->cache,
lockdep_is_held(&s->cache_lock));
if (cache) {
/* entry in cache - just bump to the head of LRU list */
list_move(&cache->lru_member, &s->cache_lru_list);
goto out_unlock;
}
cache = kmalloc(sizeof(struct sidtab_str_cache) + str_len, GFP_ATOMIC);
if (!cache)
goto out_unlock;
if (s->cache_free_slots == 0) {
/* pop a cache entry from the tail and free it */
victim = container_of(s->cache_lru_list.prev,
struct sidtab_str_cache, lru_member);
list_del(&victim->lru_member);
rcu_assign_pointer(victim->parent->cache, NULL);
} else {
s->cache_free_slots--;
}
cache->parent = entry;
cache->len = str_len;
memcpy(cache->str, str, str_len);
list_add(&cache->lru_member, &s->cache_lru_list);
rcu_assign_pointer(entry->cache, cache);
out_unlock:
spin_unlock(&s->cache_lock);
kfree_rcu(victim, rcu_member);
}
int sidtab_sid2str_get(struct sidtab *s, struct sidtab_entry *entry,
char **out, u32 *out_len)
{
struct sidtab_str_cache *cache;
int rc = 0;
if (entry->context.len)
return -ENOENT; /* do not cache invalid contexts */
rcu_read_lock();
cache = rcu_dereference(entry->cache);
if (!cache) {
rc = -ENOENT;
} else {
*out_len = cache->len;
if (out) {
*out = kmemdup(cache->str, cache->len, GFP_ATOMIC);
if (!*out)
rc = -ENOMEM;
}
}
rcu_read_unlock();
if (!rc && out)
sidtab_sid2str_put(s, entry, *out, *out_len);
return rc;
}
#endif /* CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 */
......@@ -13,16 +13,19 @@
#include <linux/spinlock_types.h>
#include <linux/log2.h>
#include <linux/hashtable.h>
#include "context.h"
struct sidtab_entry_leaf {
struct sidtab_entry {
u32 sid;
struct context context;
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
struct sidtab_str_cache __rcu *cache;
#endif
struct hlist_node list;
};
struct sidtab_node_inner;
struct sidtab_node_leaf;
union sidtab_entry_inner {
struct sidtab_node_inner *ptr_inner;
struct sidtab_node_leaf *ptr_leaf;
......@@ -38,7 +41,7 @@ union sidtab_entry_inner {
(SIDTAB_NODE_ALLOC_SHIFT - size_to_shift(sizeof(union sidtab_entry_inner)))
#define SIDTAB_INNER_ENTRIES ((size_t)1 << SIDTAB_INNER_SHIFT)
#define SIDTAB_LEAF_ENTRIES \
(SIDTAB_NODE_ALLOC_SIZE / sizeof(struct sidtab_entry_leaf))
(SIDTAB_NODE_ALLOC_SIZE / sizeof(struct sidtab_entry))
#define SIDTAB_MAX_BITS 32
#define SIDTAB_MAX U32_MAX
......@@ -48,7 +51,7 @@ union sidtab_entry_inner {
SIDTAB_INNER_SHIFT)
struct sidtab_node_leaf {
struct sidtab_entry_leaf entries[SIDTAB_LEAF_ENTRIES];
struct sidtab_entry entries[SIDTAB_LEAF_ENTRIES];
};
struct sidtab_node_inner {
......@@ -57,7 +60,7 @@ struct sidtab_node_inner {
struct sidtab_isid_entry {
int set;
struct context context;
struct sidtab_entry entry;
};
struct sidtab_convert_params {
......@@ -66,7 +69,8 @@ struct sidtab_convert_params {
struct sidtab *target;
};
#define SIDTAB_RCACHE_SIZE 3
#define SIDTAB_HASH_BITS CONFIG_SECURITY_SELINUX_SIDTAB_HASH_BITS
#define SIDTAB_HASH_BUCKETS (1 << SIDTAB_HASH_BITS)
struct sidtab {
/*
......@@ -83,17 +87,38 @@ struct sidtab {
struct sidtab_convert_params *convert;
spinlock_t lock;
/* reverse lookup cache - access atomically via {READ|WRITE}_ONCE() */
u32 rcache[SIDTAB_RCACHE_SIZE];
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
/* SID -> context string cache */
u32 cache_free_slots;
struct list_head cache_lru_list;
spinlock_t cache_lock;
#endif
/* index == SID - 1 (no entry for SECSID_NULL) */
struct sidtab_isid_entry isids[SECINITSID_NUM];
/* Hash table for fast reverse context-to-sid lookups. */
DECLARE_HASHTABLE(context_to_sid, SIDTAB_HASH_BITS);
};
int sidtab_init(struct sidtab *s);
int sidtab_set_initial(struct sidtab *s, u32 sid, struct context *context);
struct context *sidtab_search(struct sidtab *s, u32 sid);
struct context *sidtab_search_force(struct sidtab *s, u32 sid);
struct sidtab_entry *sidtab_search_entry(struct sidtab *s, u32 sid);
struct sidtab_entry *sidtab_search_entry_force(struct sidtab *s, u32 sid);
static inline struct context *sidtab_search(struct sidtab *s, u32 sid)
{
struct sidtab_entry *entry = sidtab_search_entry(s, sid);
return entry ? &entry->context : NULL;
}
static inline struct context *sidtab_search_force(struct sidtab *s, u32 sid)
{
struct sidtab_entry *entry = sidtab_search_entry_force(s, sid);
return entry ? &entry->context : NULL;
}
int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params);
......@@ -101,6 +126,27 @@ int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid);
void sidtab_destroy(struct sidtab *s);
int sidtab_hash_stats(struct sidtab *sidtab, char *page);
#if CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0
void sidtab_sid2str_put(struct sidtab *s, struct sidtab_entry *entry,
const char *str, u32 str_len);
int sidtab_sid2str_get(struct sidtab *s, struct sidtab_entry *entry,
char **out, u32 *out_len);
#else
static inline void sidtab_sid2str_put(struct sidtab *s,
struct sidtab_entry *entry,
const char *str, u32 str_len)
{
}
static inline int sidtab_sid2str_get(struct sidtab *s,
struct sidtab_entry *entry,
char **out, u32 *out_len)
{
return -ENOENT;
}
#endif /* CONFIG_SECURITY_SELINUX_SID2STR_CACHE_SIZE > 0 */
#endif /* _SS_SIDTAB_H_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment