summaryrefslogtreecommitdiff
path: root/security
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2020-10-13 16:29:55 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2020-10-13 16:29:55 -0700
commit7b540812cc0635e6c7e508359115c7cb6886fd2f (patch)
treea8e753d90bd36925a88dc9b86b5b1386bec6436c /security
parent01fb1e2f42d607ef5eb7a7ca54a0f0901fb5856c (diff)
parent0d50f059c4cdc9e436f6f4db8779ac0795bfdadf (diff)
downloadlinux-next-7b540812cc0635e6c7e508359115c7cb6886fd2f.tar.gz
Merge tag 'selinux-pr-20201012' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux
Pull selinux updates from Paul Moore: "A decent number of SELinux patches for v5.10, twenty two in total. The highlights are listed below, but all of the patches pass our test suite and merge cleanly. - A number of changes to how the SELinux policy is loaded and managed inside the kernel with the goal of improving the atomicity of a SELinux policy load operation. These changes account for the bulk of the diffstat as well as the patch count. A special thanks to everyone who contributed patches and fixes for this work. - Convert the SELinux policy read-write lock to RCU. - A tracepoint was added for audited SELinux access control events; this should help provide a more unified backtrace across kernel and userspace. - Allow the removal of security.selinux xattrs when a SELinux policy is not loaded. - Enable policy capabilities in SELinux policies created with the scripts/selinux/mdp tool. - Provide some "no sooner than" dates for the SELinux checkreqprot sysfs deprecation" * tag 'selinux-pr-20201012' of git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux: (22 commits) selinux: provide a "no sooner than" date for the checkreqprot removal selinux: Add helper functions to get and set checkreqprot selinux: access policycaps with READ_ONCE/WRITE_ONCE selinux: simplify away security_policydb_len() selinux: move policy mutex to selinux_state, use in lockdep checks selinux: fix error handling bugs in security_load_policy() selinux: convert policy read-write lock to RCU selinux: delete repeated words in comments selinux: add basic filtering for audit trace events selinux: add tracepoint on audited events selinux: Create new booleans and class dirs out of tree selinux: Standardize string literal usage for selinuxfs directory names selinux: Refactor selinuxfs directory populating functions selinux: Create function for selinuxfs directory cleanup selinux: permit removing security.selinux xattr before policy load selinux: fix memdup.cocci warnings selinux: avoid dereferencing the policy prior to initialization selinux: fix allocation failure check on newpolicy->sidtab selinux: refactor changing booleans selinux: move policy commit after updating selinuxfs ...
Diffstat (limited to 'security')
-rw-r--r--security/selinux/avc.c29
-rw-r--r--security/selinux/hooks.c17
-rw-r--r--security/selinux/include/conditional.h2
-rw-r--r--security/selinux/include/policycap.h20
-rw-r--r--security/selinux/include/policycap_names.h18
-rw-r--r--security/selinux/include/security.h63
-rw-r--r--security/selinux/selinuxfs.c259
-rw-r--r--security/selinux/ss/avtab.c49
-rw-r--r--security/selinux/ss/avtab.h1
-rw-r--r--security/selinux/ss/conditional.c155
-rw-r--r--security/selinux/ss/conditional.h2
-rw-r--r--security/selinux/ss/hashtab.c53
-rw-r--r--security/selinux/ss/hashtab.h6
-rw-r--r--security/selinux/ss/services.c875
-rw-r--r--security/selinux/ss/services.h5
-rw-r--r--security/selinux/ss/sidtab.c10
-rw-r--r--security/selinux/ss/sidtab.h2
17 files changed, 1068 insertions, 498 deletions
diff --git a/security/selinux/avc.c b/security/selinux/avc.c
index d18cb32a242a..3c05827608b6 100644
--- a/security/selinux/avc.c
+++ b/security/selinux/avc.c
@@ -31,6 +31,9 @@
#include "avc_ss.h"
#include "classmap.h"
+#define CREATE_TRACE_POINTS
+#include <trace/events/avc.h>
+
#define AVC_CACHE_SLOTS 512
#define AVC_DEF_CACHE_THRESHOLD 512
#define AVC_CACHE_RECLAIM 16
@@ -702,33 +705,37 @@ static void avc_audit_post_callback(struct audit_buffer *ab, void *a)
{
struct common_audit_data *ad = a;
struct selinux_audit_data *sad = ad->selinux_audit_data;
- char *scontext;
+ char *scontext = NULL;
+ char *tcontext = NULL;
+ const char *tclass = NULL;
u32 scontext_len;
+ u32 tcontext_len;
int rc;
rc = security_sid_to_context(sad->state, sad->ssid, &scontext,
&scontext_len);
if (rc)
audit_log_format(ab, " ssid=%d", sad->ssid);
- else {
+ else
audit_log_format(ab, " scontext=%s", scontext);
- kfree(scontext);
- }
- rc = security_sid_to_context(sad->state, sad->tsid, &scontext,
- &scontext_len);
+ rc = security_sid_to_context(sad->state, sad->tsid, &tcontext,
+ &tcontext_len);
if (rc)
audit_log_format(ab, " tsid=%d", sad->tsid);
- else {
- audit_log_format(ab, " tcontext=%s", scontext);
- kfree(scontext);
- }
+ else
+ audit_log_format(ab, " tcontext=%s", tcontext);
- audit_log_format(ab, " tclass=%s", secclass_map[sad->tclass-1].name);
+ tclass = secclass_map[sad->tclass-1].name;
+ audit_log_format(ab, " tclass=%s", tclass);
if (sad->denied)
audit_log_format(ab, " permissive=%u", sad->result ? 0 : 1);
+ trace_selinux_audited(sad, scontext, tcontext, tclass);
+ kfree(tcontext);
+ kfree(scontext);
+
/* in case of invalid context report also the actual context string */
rc = security_sid_to_context_inval(sad->state, sad->ssid, &scontext,
&scontext_len);
diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index a340986aa92e..2dabd58b126a 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -1978,7 +1978,7 @@ static inline u32 file_to_av(struct file *file)
}
/*
- * Convert a file to an access vector and include the correct open
+ * Convert a file to an access vector and include the correct
* open permission.
*/
static inline u32 open_file_to_av(struct file *file)
@@ -3271,6 +3271,9 @@ static int selinux_inode_removexattr(struct dentry *dentry, const char *name)
return dentry_has_perm(current_cred(), dentry, FILE__SETATTR);
}
+ if (!selinux_initialized(&selinux_state))
+ return 0;
+
/* No one is allowed to remove a SELinux security label.
You can change the label, but all data must be labeled. */
return -EACCES;
@@ -3709,7 +3712,7 @@ static int selinux_mmap_file(struct file *file, unsigned long reqprot,
return rc;
}
- if (selinux_state.checkreqprot)
+ if (checkreqprot_get(&selinux_state))
prot = reqprot;
return file_map_prot_check(file, prot,
@@ -3723,7 +3726,7 @@ static int selinux_file_mprotect(struct vm_area_struct *vma,
const struct cred *cred = current_cred();
u32 sid = cred_sid(cred);
- if (selinux_state.checkreqprot)
+ if (checkreqprot_get(&selinux_state))
prot = reqprot;
if (default_noexec &&
@@ -4438,7 +4441,7 @@ static int selinux_skb_peerlbl_sid(struct sk_buff *skb, u16 family, u32 *sid)
*
* If @skb_sid is valid then the user:role:type information from @sk_sid is
* combined with the MLS information from @skb_sid in order to create
- * @conn_sid. If @skb_sid is not valid then then @conn_sid is simply a copy
+ * @conn_sid. If @skb_sid is not valid then @conn_sid is simply a copy
* of @sk_sid. Returns zero on success, negative values on failure.
*
*/
@@ -5308,7 +5311,7 @@ static int selinux_sctp_bind_connect(struct sock *sk, int optname,
/* As selinux_sctp_bind_connect() is called by the
* SCTP protocol layer, the socket is already locked,
- * therefore selinux_netlbl_socket_connect_locked() is
+ * therefore selinux_netlbl_socket_connect_locked()
* is called here. The situations handled are:
* sctp_connectx(3), sctp_sendmsg(3), sendmsg(2),
* whenever a new IP address is added or when a new
@@ -7225,10 +7228,10 @@ static __init int selinux_init(void)
memset(&selinux_state, 0, sizeof(selinux_state));
enforcing_set(&selinux_state, selinux_enforcing_boot);
- selinux_state.checkreqprot = selinux_checkreqprot_boot;
- selinux_ss_init(&selinux_state.ss);
+ checkreqprot_set(&selinux_state, selinux_checkreqprot_boot);
selinux_avc_init(&selinux_state.avc);
mutex_init(&selinux_state.status_lock);
+ mutex_init(&selinux_state.policy_mutex);
/* Set the security state for the initial task. */
cred_init_security();
diff --git a/security/selinux/include/conditional.h b/security/selinux/include/conditional.h
index 539ab357707d..b09343346e3f 100644
--- a/security/selinux/include/conditional.h
+++ b/security/selinux/include/conditional.h
@@ -13,7 +13,7 @@
#include "security.h"
-int security_get_bools(struct selinux_state *state,
+int security_get_bools(struct selinux_policy *policy,
u32 *len, char ***names, int **values);
int security_set_bools(struct selinux_state *state, u32 len, int *values);
diff --git a/security/selinux/include/policycap.h b/security/selinux/include/policycap.h
new file mode 100644
index 000000000000..2ec038efbb03
--- /dev/null
+++ b/security/selinux/include/policycap.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SELINUX_POLICYCAP_H_
+#define _SELINUX_POLICYCAP_H_
+
+/* Policy capabilities */
+enum {
+ POLICYDB_CAPABILITY_NETPEER,
+ POLICYDB_CAPABILITY_OPENPERM,
+ POLICYDB_CAPABILITY_EXTSOCKCLASS,
+ POLICYDB_CAPABILITY_ALWAYSNETWORK,
+ POLICYDB_CAPABILITY_CGROUPSECLABEL,
+ POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION,
+ POLICYDB_CAPABILITY_GENFS_SECLABEL_SYMLINKS,
+ __POLICYDB_CAPABILITY_MAX
+};
+#define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1)
+
+extern const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX];
+
+#endif /* _SELINUX_POLICYCAP_H_ */
diff --git a/security/selinux/include/policycap_names.h b/security/selinux/include/policycap_names.h
new file mode 100644
index 000000000000..b89289f092c9
--- /dev/null
+++ b/security/selinux/include/policycap_names.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _SELINUX_POLICYCAP_NAMES_H_
+#define _SELINUX_POLICYCAP_NAMES_H_
+
+#include "policycap.h"
+
+/* Policy capability names */
+const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = {
+ "network_peer_controls",
+ "open_perms",
+ "extended_socket_class",
+ "always_check_network",
+ "cgroup_seclabel",
+ "nnp_nosuid_transition",
+ "genfs_seclabel_symlinks"
+};
+
+#endif /* _SELINUX_POLICYCAP_NAMES_H_ */
diff --git a/security/selinux/include/security.h b/security/selinux/include/security.h
index b0e02cfe3ce1..3cc8bab31ea8 100644
--- a/security/selinux/include/security.h
+++ b/security/selinux/include/security.h
@@ -13,9 +13,11 @@
#include <linux/dcache.h>
#include <linux/magic.h>
#include <linux/types.h>
+#include <linux/rcupdate.h>
#include <linux/refcount.h>
#include <linux/workqueue.h>
#include "flask.h"
+#include "policycap.h"
#define SECSID_NULL 0x00000000 /* unspecified SID */
#define SECSID_WILD 0xffffffff /* wildcard SID */
@@ -72,21 +74,6 @@ struct netlbl_lsm_secattr;
extern int selinux_enabled_boot;
-/* Policy capabilities */
-enum {
- POLICYDB_CAPABILITY_NETPEER,
- POLICYDB_CAPABILITY_OPENPERM,
- POLICYDB_CAPABILITY_EXTSOCKCLASS,
- POLICYDB_CAPABILITY_ALWAYSNETWORK,
- POLICYDB_CAPABILITY_CGROUPSECLABEL,
- POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION,
- POLICYDB_CAPABILITY_GENFS_SECLABEL_SYMLINKS,
- __POLICYDB_CAPABILITY_MAX
-};
-#define POLICYDB_CAPABILITY_MAX (__POLICYDB_CAPABILITY_MAX - 1)
-
-extern const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX];
-
/*
* type_datum properties
* available at the kernel policy version >= POLICYDB_VERSION_BOUNDARY
@@ -98,7 +85,7 @@ extern const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX];
#define POLICYDB_BOUNDS_MAXDEPTH 4
struct selinux_avc;
-struct selinux_ss;
+struct selinux_policy;
struct selinux_state {
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
@@ -115,10 +102,10 @@ struct selinux_state {
struct mutex status_lock;
struct selinux_avc *avc;
- struct selinux_ss *ss;
+ struct selinux_policy __rcu *policy;
+ struct mutex policy_mutex;
} __randomize_layout;
-void selinux_ss_init(struct selinux_ss **ss);
void selinux_avc_init(struct selinux_avc **avc);
extern struct selinux_state selinux_state;
@@ -156,6 +143,16 @@ static inline void enforcing_set(struct selinux_state *state, bool value)
}
#endif
+static inline bool checkreqprot_get(const struct selinux_state *state)
+{
+ return READ_ONCE(state->checkreqprot);
+}
+
+static inline void checkreqprot_set(struct selinux_state *state, bool value)
+{
+ WRITE_ONCE(state->checkreqprot, value);
+}
+
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
static inline bool selinux_disabled(struct selinux_state *state)
{
@@ -177,57 +174,61 @@ static inline bool selinux_policycap_netpeer(void)
{
struct selinux_state *state = &selinux_state;
- return state->policycap[POLICYDB_CAPABILITY_NETPEER];
+ return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_NETPEER]);
}
static inline bool selinux_policycap_openperm(void)
{
struct selinux_state *state = &selinux_state;
- return state->policycap[POLICYDB_CAPABILITY_OPENPERM];
+ return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_OPENPERM]);
}
static inline bool selinux_policycap_extsockclass(void)
{
struct selinux_state *state = &selinux_state;
- return state->policycap[POLICYDB_CAPABILITY_EXTSOCKCLASS];
+ return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_EXTSOCKCLASS]);
}
static inline bool selinux_policycap_alwaysnetwork(void)
{
struct selinux_state *state = &selinux_state;
- return state->policycap[POLICYDB_CAPABILITY_ALWAYSNETWORK];
+ return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_ALWAYSNETWORK]);
}
static inline bool selinux_policycap_cgroupseclabel(void)
{
struct selinux_state *state = &selinux_state;
- return state->policycap[POLICYDB_CAPABILITY_CGROUPSECLABEL];
+ return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_CGROUPSECLABEL]);
}
static inline bool selinux_policycap_nnp_nosuid_transition(void)
{
struct selinux_state *state = &selinux_state;
- return state->policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION];
+ return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_NNP_NOSUID_TRANSITION]);
}
static inline bool selinux_policycap_genfs_seclabel_symlinks(void)
{
struct selinux_state *state = &selinux_state;
- return state->policycap[POLICYDB_CAPABILITY_GENFS_SECLABEL_SYMLINKS];
+ return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_GENFS_SECLABEL_SYMLINKS]);
}
int security_mls_enabled(struct selinux_state *state);
int security_load_policy(struct selinux_state *state,
- void *data, size_t len);
+ void *data, size_t len,
+ struct selinux_policy **newpolicyp);
+void selinux_policy_commit(struct selinux_state *state,
+ struct selinux_policy *newpolicy);
+void selinux_policy_cancel(struct selinux_state *state,
+ struct selinux_policy *policy);
int security_read_policy(struct selinux_state *state,
void **data, size_t *len);
-size_t security_policydb_len(struct selinux_state *state);
int security_policycap_supported(struct selinux_state *state,
unsigned int req_cap);
@@ -358,9 +359,9 @@ int security_net_peersid_resolve(struct selinux_state *state,
u32 xfrm_sid,
u32 *peer_sid);
-int security_get_classes(struct selinux_state *state,
+int security_get_classes(struct selinux_policy *policy,
char ***classes, int *nclasses);
-int security_get_permissions(struct selinux_state *state,
+int security_get_permissions(struct selinux_policy *policy,
char *class, char ***perms, int *nperms);
int security_get_reject_unknown(struct selinux_state *state);
int security_get_allow_unknown(struct selinux_state *state);
@@ -380,6 +381,10 @@ int security_genfs_sid(struct selinux_state *state,
const char *fstype, char *name, u16 sclass,
u32 *sid);
+int selinux_policy_genfs_sid(struct selinux_policy *policy,
+ const char *fstype, char *name, u16 sclass,
+ u32 *sid);
+
#ifdef CONFIG_NETLABEL
int security_netlbl_secattr_to_sid(struct selinux_state *state,
struct netlbl_lsm_secattr *secattr,
diff --git a/security/selinux/selinuxfs.c b/security/selinux/selinuxfs.c
index 4781314c2510..4bde570d56a2 100644
--- a/security/selinux/selinuxfs.c
+++ b/security/selinux/selinuxfs.c
@@ -20,6 +20,7 @@
#include <linux/fs_context.h>
#include <linux/mount.h>
#include <linux/mutex.h>
+#include <linux/namei.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/security.h>
@@ -74,7 +75,6 @@ struct selinux_fs_info {
unsigned long last_class_ino;
bool policy_opened;
struct dentry *policycap_dir;
- struct mutex mutex;
unsigned long last_ino;
struct selinux_state *state;
struct super_block *sb;
@@ -88,7 +88,6 @@ static int selinux_fs_info_create(struct super_block *sb)
if (!fsi)
return -ENOMEM;
- mutex_init(&fsi->mutex);
fsi->last_ino = SEL_INO_NEXT - 1;
fsi->state = &selinux_state;
fsi->sb = sb;
@@ -117,6 +116,10 @@ static void selinux_fs_info_free(struct super_block *sb)
#define SEL_POLICYCAP_INO_OFFSET 0x08000000
#define SEL_INO_MASK 0x00ffffff
+#define BOOL_DIR_NAME "booleans"
+#define CLASS_DIR_NAME "class"
+#define POLICYCAP_DIR_NAME "policy_capabilities"
+
#define TMPBUFLEN 12
static ssize_t sel_read_enforce(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
@@ -346,14 +349,24 @@ static const struct file_operations sel_policyvers_ops = {
};
/* declaration for sel_write_load */
-static int sel_make_bools(struct selinux_fs_info *fsi);
-static int sel_make_classes(struct selinux_fs_info *fsi);
-static int sel_make_policycap(struct selinux_fs_info *fsi);
+static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_dir,
+ unsigned int *bool_num, char ***bool_pending_names,
+ unsigned int **bool_pending_values);
+static int sel_make_classes(struct selinux_policy *newpolicy,
+ struct dentry *class_dir,
+ unsigned long *last_class_ino);
/* declaration for sel_make_class_dirs */
static struct dentry *sel_make_dir(struct dentry *dir, const char *name,
unsigned long *ino);
+/* declaration for sel_make_policy_nodes */
+static struct dentry *sel_make_disconnected_dir(struct super_block *sb,
+ unsigned long *ino);
+
+/* declaration for sel_make_policy_nodes */
+static void sel_remove_entries(struct dentry *de);
+
static ssize_t sel_read_mls(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
@@ -385,7 +398,7 @@ static int sel_open_policy(struct inode *inode, struct file *filp)
BUG_ON(filp->private_data);
- mutex_lock(&fsi->mutex);
+ mutex_lock(&fsi->state->policy_mutex);
rc = avc_has_perm(&selinux_state,
current_sid(), SECINITSID_SECURITY,
@@ -402,25 +415,25 @@ static int sel_open_policy(struct inode *inode, struct file *filp)
if (!plm)
goto err;
- if (i_size_read(inode) != security_policydb_len(state)) {
- inode_lock(inode);
- i_size_write(inode, security_policydb_len(state));
- inode_unlock(inode);
- }
-
rc = security_read_policy(state, &plm->data, &plm->len);
if (rc)
goto err;
+ if ((size_t)i_size_read(inode) != plm->len) {
+ inode_lock(inode);
+ i_size_write(inode, plm->len);
+ inode_unlock(inode);
+ }
+
fsi->policy_opened = 1;
filp->private_data = plm;
- mutex_unlock(&fsi->mutex);
+ mutex_unlock(&fsi->state->policy_mutex);
return 0;
err:
- mutex_unlock(&fsi->mutex);
+ mutex_unlock(&fsi->state->policy_mutex);
if (plm)
vfree(plm->data);
@@ -508,29 +521,94 @@ static const struct file_operations sel_policy_ops = {
.llseek = generic_file_llseek,
};
-static int sel_make_policy_nodes(struct selinux_fs_info *fsi)
+static void sel_remove_old_bool_data(unsigned int bool_num, char **bool_names,
+ unsigned int *bool_values)
{
- int ret;
+ u32 i;
+
+ /* bool_dir cleanup */
+ for (i = 0; i < bool_num; i++)
+ kfree(bool_names[i]);
+ kfree(bool_names);
+ kfree(bool_values);
+}
- ret = sel_make_bools(fsi);
+static int sel_make_policy_nodes(struct selinux_fs_info *fsi,
+ struct selinux_policy *newpolicy)
+{
+ int ret = 0;
+ struct dentry *tmp_parent, *tmp_bool_dir, *tmp_class_dir, *old_dentry;
+ unsigned int tmp_bool_num, old_bool_num;
+ char **tmp_bool_names, **old_bool_names;
+ unsigned int *tmp_bool_values, *old_bool_values;
+ unsigned long tmp_ino = fsi->last_ino; /* Don't increment last_ino in this function */
+
+ tmp_parent = sel_make_disconnected_dir(fsi->sb, &tmp_ino);
+ if (IS_ERR(tmp_parent))
+ return PTR_ERR(tmp_parent);
+
+ tmp_ino = fsi->bool_dir->d_inode->i_ino - 1; /* sel_make_dir will increment and set */
+ tmp_bool_dir = sel_make_dir(tmp_parent, BOOL_DIR_NAME, &tmp_ino);
+ if (IS_ERR(tmp_bool_dir)) {
+ ret = PTR_ERR(tmp_bool_dir);
+ goto out;
+ }
+
+ tmp_ino = fsi->class_dir->d_inode->i_ino - 1; /* sel_make_dir will increment and set */
+ tmp_class_dir = sel_make_dir(tmp_parent, CLASS_DIR_NAME, &tmp_ino);
+ if (IS_ERR(tmp_class_dir)) {
+ ret = PTR_ERR(tmp_class_dir);
+ goto out;
+ }
+
+ ret = sel_make_bools(newpolicy, tmp_bool_dir, &tmp_bool_num,
+ &tmp_bool_names, &tmp_bool_values);
if (ret) {
pr_err("SELinux: failed to load policy booleans\n");
- return ret;
+ goto out;
}
- ret = sel_make_classes(fsi);
+ ret = sel_make_classes(newpolicy, tmp_class_dir,
+ &fsi->last_class_ino);
if (ret) {
pr_err("SELinux: failed to load policy classes\n");
- return ret;
+ goto out;
}
- ret = sel_make_policycap(fsi);
- if (ret) {
- pr_err("SELinux: failed to load policy capabilities\n");
- return ret;
- }
+ /* booleans */
+ old_dentry = fsi->bool_dir;
+ lock_rename(tmp_bool_dir, old_dentry);
+ d_exchange(tmp_bool_dir, fsi->bool_dir);
- return 0;
+ old_bool_num = fsi->bool_num;
+ old_bool_names = fsi->bool_pending_names;
+ old_bool_values = fsi->bool_pending_values;
+
+ fsi->bool_num = tmp_bool_num;
+ fsi->bool_pending_names = tmp_bool_names;
+ fsi->bool_pending_values = tmp_bool_values;
+
+ sel_remove_old_bool_data(old_bool_num, old_bool_names, old_bool_values);
+
+ fsi->bool_dir = tmp_bool_dir;
+ unlock_rename(tmp_bool_dir, old_dentry);
+
+ /* classes */
+ old_dentry = fsi->class_dir;
+ lock_rename(tmp_class_dir, old_dentry);
+ d_exchange(tmp_class_dir, fsi->class_dir);
+ fsi->class_dir = tmp_class_dir;
+ unlock_rename(tmp_class_dir, old_dentry);
+
+out:
+ /* Since the other temporary dirs are children of tmp_parent
+ * this will handle all the cleanup in the case of a failure before
+ * the swapover
+ */
+ sel_remove_entries(tmp_parent);
+ dput(tmp_parent); /* d_genocide() only handles the children */
+
+ return ret;
}
static ssize_t sel_write_load(struct file *file, const char __user *buf,
@@ -538,10 +616,11 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
{
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
+ struct selinux_policy *newpolicy;
ssize_t length;
void *data = NULL;
- mutex_lock(&fsi->mutex);
+ mutex_lock(&fsi->state->policy_mutex);
length = avc_has_perm(&selinux_state,
current_sid(), SECINITSID_SECURITY,
@@ -563,15 +642,19 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
if (copy_from_user(data, buf, count) != 0)
goto out;
- length = security_load_policy(fsi->state, data, count);
+ length = security_load_policy(fsi->state, data, count, &newpolicy);
if (length) {
pr_warn_ratelimited("SELinux: failed to load policy\n");
goto out;
}
- length = sel_make_policy_nodes(fsi);
- if (length)
+ length = sel_make_policy_nodes(fsi, newpolicy);
+ if (length) {
+ selinux_policy_cancel(fsi->state, newpolicy);
goto out1;
+ }
+
+ selinux_policy_commit(fsi->state, newpolicy);
length = count;
@@ -581,7 +664,7 @@ out1:
from_kuid(&init_user_ns, audit_get_loginuid(current)),
audit_get_sessionid(current));
out:
- mutex_unlock(&fsi->mutex);
+ mutex_unlock(&fsi->state->policy_mutex);
vfree(data);
return length;
}
@@ -634,7 +717,8 @@ static ssize_t sel_read_checkreqprot(struct file *filp, char __user *buf,
char tmpbuf[TMPBUFLEN];
ssize_t length;
- length = scnprintf(tmpbuf, TMPBUFLEN, "%u", fsi->state->checkreqprot);
+ length = scnprintf(tmpbuf, TMPBUFLEN, "%u",
+ checkreqprot_get(fsi->state));
return simple_read_from_buffer(buf, count, ppos, tmpbuf, length);
}
@@ -676,7 +760,7 @@ static ssize_t sel_write_checkreqprot(struct file *file, const char __user *buf,
comm, current->pid);
}
- fsi->state->checkreqprot = new_value ? 1 : 0;
+ checkreqprot_set(fsi->state, (new_value ? 1 : 0));
length = count;
out:
kfree(page);
@@ -1186,7 +1270,7 @@ static ssize_t sel_read_bool(struct file *filep, char __user *buf,
unsigned index = file_inode(filep)->i_ino & SEL_INO_MASK;
const char *name = filep->f_path.dentry->d_name.name;
- mutex_lock(&fsi->mutex);
+ mutex_lock(&fsi->state->policy_mutex);
ret = -EINVAL;
if (index >= fsi->bool_num || strcmp(name,
@@ -1205,14 +1289,14 @@ static ssize_t sel_read_bool(struct file *filep, char __user *buf,
}
length = scnprintf(page, PAGE_SIZE, "%d %d", cur_enforcing,
fsi->bool_pending_values[index]);
- mutex_unlock(&fsi->mutex);
+ mutex_unlock(&fsi->state->policy_mutex);
ret = simple_read_from_buffer(buf, count, ppos, page, length);
out_free:
free_page((unsigned long)page);
return ret;
out_unlock:
- mutex_unlock(&fsi->mutex);
+ mutex_unlock(&fsi->state->policy_mutex);
goto out_free;
}
@@ -1237,7 +1321,7 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf,
if (IS_ERR(page))
return PTR_ERR(page);
- mutex_lock(&fsi->mutex);
+ mutex_lock(&fsi->state->policy_mutex);
length = avc_has_perm(&selinux_state,
current_sid(), SECINITSID_SECURITY,
@@ -1262,7 +1346,7 @@ static ssize_t sel_write_bool(struct file *filep, const char __user *buf,
length = count;
out:
- mutex_unlock(&fsi->mutex);
+ mutex_unlock(&fsi->state->policy_mutex);
kfree(page);
return length;
}
@@ -1293,7 +1377,7 @@ static ssize_t sel_commit_bools_write(struct file *filep,
if (IS_ERR(page))
return PTR_ERR(page);
- mutex_lock(&fsi->mutex);
+ mutex_lock(&fsi->state->policy_mutex);
length = avc_has_perm(&selinux_state,
current_sid(), SECINITSID_SECURITY,
@@ -1315,7 +1399,7 @@ static ssize_t sel_commit_bools_write(struct file *filep,
length = count;
out:
- mutex_unlock(&fsi->mutex);
+ mutex_unlock(&fsi->state->policy_mutex);
kfree(page);
return length;
}
@@ -1331,14 +1415,13 @@ static void sel_remove_entries(struct dentry *de)
shrink_dcache_parent(de);
}
-#define BOOL_DIR_NAME "booleans"
-
-static int sel_make_bools(struct selinux_fs_info *fsi)
+static int sel_make_bools(struct selinux_policy *newpolicy, struct dentry *bool_dir,
+ unsigned int *bool_num, char ***bool_pending_names,
+ unsigned int **bool_pending_values)
{
int ret;
ssize_t len;
struct dentry *dentry = NULL;
- struct dentry *dir = fsi->bool_dir;
struct inode *inode = NULL;
struct inode_security_struct *isec;
char **names = NULL, *page;
@@ -1346,34 +1429,23 @@ static int sel_make_bools(struct selinux_fs_info *fsi)
int *values = NULL;
u32 sid;
- /* remove any existing files */
- for (i = 0; i < fsi->bool_num; i++)
- kfree(fsi->bool_pending_names[i]);
- kfree(fsi->bool_pending_names);
- kfree(fsi->bool_pending_values);
- fsi->bool_num = 0;
- fsi->bool_pending_names = NULL;
- fsi->bool_pending_values = NULL;
-
- sel_remove_entries(dir);
-
ret = -ENOMEM;
page = (char *)get_zeroed_page(GFP_KERNEL);
if (!page)
goto out;
- ret = security_get_bools(fsi->state, &num, &names, &values);
+ ret = security_get_bools(newpolicy, &num, &names, &values);
if (ret)
goto out;
for (i = 0; i < num; i++) {
ret = -ENOMEM;
- dentry = d_alloc_name(dir, names[i]);
+ dentry = d_alloc_name(bool_dir, names[i]);
if (!dentry)
goto out;
ret = -ENOMEM;
- inode = sel_make_inode(dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR);
+ inode = sel_make_inode(bool_dir->d_sb, S_IFREG | S_IRUGO | S_IWUSR);
if (!inode) {
dput(dentry);
goto out;
@@ -1388,7 +1460,7 @@ static int sel_make_bools(struct selinux_fs_info *fsi)
}
isec = selinux_inode(inode);
- ret = security_genfs_sid(fsi->state, "selinuxfs", page,
+ ret = selinux_policy_genfs_sid(newpolicy, "selinuxfs", page,
SECCLASS_FILE, &sid);
if (ret) {
pr_warn_ratelimited("SELinux: no sid found, defaulting to security isid for %s\n",
@@ -1402,9 +1474,9 @@ static int sel_make_bools(struct selinux_fs_info *fsi)
inode->i_ino = i|SEL_BOOL_INO_OFFSET;
d_add(dentry, inode);
}
- fsi->bool_num = num;
- fsi->bool_pending_names = names;
- fsi->bool_pending_values = values;
+ *bool_num = num;
+ *bool_pending_names = names;
+ *bool_pending_values = values;
free_page((unsigned long)page);
return 0;
@@ -1417,7 +1489,7 @@ out:
kfree(names);
}
kfree(values);
- sel_remove_entries(dir);
+ sel_remove_entries(bool_dir);
return ret;
}
@@ -1791,14 +1863,14 @@ static const struct file_operations sel_policycap_ops = {
.llseek = generic_file_llseek,
};
-static int sel_make_perm_files(char *objclass, int classvalue,
- struct dentry *dir)
+static int sel_make_perm_files(struct selinux_policy *newpolicy,
+ char *objclass, int classvalue,
+ struct dentry *dir)
{
- struct selinux_fs_info *fsi = dir->d_sb->s_fs_info;
int i, rc, nperms;
char **perms;
- rc = security_get_permissions(fsi->state, objclass, &perms, &nperms);
+ rc = security_get_permissions(newpolicy, objclass, &perms, &nperms);
if (rc)
return rc;
@@ -1831,8 +1903,9 @@ out:
return rc;
}
-static int sel_make_class_dir_entries(char *classname, int index,
- struct dentry *dir)
+static int sel_make_class_dir_entries(struct selinux_policy *newpolicy,
+ char *classname, int index,
+ struct dentry *dir)
{
struct super_block *sb = dir->d_sb;
struct selinux_fs_info *fsi = sb->s_fs_info;
@@ -1858,39 +1931,38 @@ static int sel_make_class_dir_entries(char *classname, int index,
if (IS_ERR(dentry))
return PTR_ERR(dentry);
- rc = sel_make_perm_files(classname, index, dentry);
+ rc = sel_make_perm_files(newpolicy, classname, index, dentry);
return rc;
}
-static int sel_make_classes(struct selinux_fs_info *fsi)
+static int sel_make_classes(struct selinux_policy *newpolicy,
+ struct dentry *class_dir,
+ unsigned long *last_class_ino)
{
int rc, nclasses, i;
char **classes;
- /* delete any existing entries */
- sel_remove_entries(fsi->class_dir);
-
- rc = security_get_classes(fsi->state, &classes, &nclasses);
+ rc = security_get_classes(newpolicy, &classes, &nclasses);
if (rc)
return rc;
/* +2 since classes are 1-indexed */
- fsi->last_class_ino = sel_class_to_ino(nclasses + 2);
+ *last_class_ino = sel_class_to_ino(nclasses + 2);
for (i = 0; i < nclasses; i++) {
struct dentry *class_name_dir;
- class_name_dir = sel_make_dir(fsi->class_dir, classes[i],
- &fsi->last_class_ino);
+ class_name_dir = sel_make_dir(class_dir, classes[i],
+ last_class_ino);
if (IS_ERR(class_name_dir)) {
rc = PTR_ERR(class_name_dir);
goto out;
}
/* i+1 since class values are 1-indexed */
- rc = sel_make_class_dir_entries(classes[i], i + 1,
+ rc = sel_make_class_dir_entries(newpolicy, classes[i], i + 1,
class_name_dir);
if (rc)
goto out;
@@ -1909,8 +1981,6 @@ static int sel_make_policycap(struct selinux_fs_info *fsi)
struct dentry *dentry = NULL;
struct inode *inode = NULL;
- sel_remove_entries(fsi->policycap_dir);
-
for (iter = 0; iter <= POLICYDB_CAPABILITY_MAX; iter++) {
if (iter < ARRAY_SIZE(selinux_policycap_names))
dentry = d_alloc_name(fsi->policycap_dir,
@@ -1962,6 +2032,22 @@ static struct dentry *sel_make_dir(struct dentry *dir, const char *name,
return dentry;
}
+static struct dentry *sel_make_disconnected_dir(struct super_block *sb,
+ unsigned long *ino)
+{
+ struct inode *inode = sel_make_inode(sb, S_IFDIR | S_IRUGO | S_IXUGO);
+
+ if (!inode)
+ return ERR_PTR(-ENOMEM);
+
+ inode->i_op = &simple_dir_inode_operations;
+ inode->i_fop = &simple_dir_operations;
+ inode->i_ino = ++(*ino);
+ /* directory inodes start off with i_nlink == 2 (for "." entry) */
+ inc_nlink(inode);
+ return d_obtain_alias(inode);
+}
+
#define NULL_FILE_NAME "null"
static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
@@ -2060,14 +2146,14 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
if (ret)
goto err;
- fsi->class_dir = sel_make_dir(sb->s_root, "class", &fsi->last_ino);
+ fsi->class_dir = sel_make_dir(sb->s_root, CLASS_DIR_NAME, &fsi->last_ino);
if (IS_ERR(fsi->class_dir)) {
ret = PTR_ERR(fsi->class_dir);
fsi->class_dir = NULL;
goto err;
}
- fsi->policycap_dir = sel_make_dir(sb->s_root, "policy_capabilities",
+ fsi->policycap_dir = sel_make_dir(sb->s_root, POLICYCAP_DIR_NAME,
&fsi->last_ino);
if (IS_ERR(fsi->policycap_dir)) {
ret = PTR_ERR(fsi->policycap_dir);
@@ -2075,9 +2161,12 @@ static int sel_fill_super(struct super_block *sb, struct fs_context *fc)
goto err;
}
- ret = sel_make_policy_nodes(fsi);
- if (ret)
+ ret = sel_make_policycap(fsi);
+ if (ret) {
+ pr_err("SELinux: failed to load policy capabilities\n");
goto err;
+ }
+
return 0;
err:
pr_err("SELinux: %s: failed while creating inodes\n",
diff --git a/security/selinux/ss/avtab.c b/security/selinux/ss/avtab.c
index 01b300a4a882..0172d87e2b9a 100644
--- a/security/selinux/ss/avtab.c
+++ b/security/selinux/ss/avtab.c
@@ -301,7 +301,6 @@ void avtab_destroy(struct avtab *h)
void avtab_init(struct avtab *h)
{
- kvfree(h->htable);
h->htable = NULL;
h->nel = 0;
}
@@ -340,6 +339,54 @@ int avtab_alloc(struct avtab *h, u32 nrules)
return 0;
}
+int avtab_duplicate(struct avtab *new, struct avtab *orig)
+{
+ int i;
+ struct avtab_node *node, *tmp, *tail;
+
+ memset(new, 0, sizeof(*new));
+
+ new->htable = kvcalloc(orig->nslot, sizeof(void *), GFP_KERNEL);
+ if (!new->htable)
+ return -ENOMEM;
+ new->nslot = orig->nslot;
+ new->mask = orig->mask;
+
+ for (i = 0; i < orig->nslot; i++) {
+ tail = NULL;
+ for (node = orig->htable[i]; node; node = node->next) {
+ tmp = kmem_cache_zalloc(avtab_node_cachep, GFP_KERNEL);
+ if (!tmp)
+ goto error;
+ tmp->key = node->key;
+ if (tmp->key.specified & AVTAB_XPERMS) {
+ tmp->datum.u.xperms =
+ kmem_cache_zalloc(avtab_xperms_cachep,
+ GFP_KERNEL);
+ if (!tmp->datum.u.xperms) {
+ kmem_cache_free(avtab_node_cachep, tmp);
+ goto error;
+ }
+ tmp->datum.u.xperms = node->datum.u.xperms;
+ } else
+ tmp->datum.u.data = node->datum.u.data;
+
+ if (tail)
+ tail->next = tmp;
+ else
+ new->htable[i] = tmp;
+
+ tail = tmp;
+ new->nel++;
+ }
+ }
+
+ return 0;
+error:
+ avtab_destroy(new);
+ return -ENOMEM;
+}
+
void avtab_hash_eval(struct avtab *h, char *tag)
{
int i, chain_len, slots_used, max_chain_len;
diff --git a/security/selinux/ss/avtab.h b/security/selinux/ss/avtab.h
index 5fdcb6696bcc..4c4445ca9118 100644
--- a/security/selinux/ss/avtab.h
+++ b/security/selinux/ss/avtab.h
@@ -89,6 +89,7 @@ struct avtab {
void avtab_init(struct avtab *h);
int avtab_alloc(struct avtab *, u32);
+int avtab_duplicate(struct avtab *new, struct avtab *orig);
struct avtab_datum *avtab_search(struct avtab *h, struct avtab_key *k);
void avtab_destroy(struct avtab *h);
void avtab_hash_eval(struct avtab *h, char *tag);
diff --git a/security/selinux/ss/conditional.c b/security/selinux/ss/conditional.c
index 5a47258c1d77..0b32f3ab025e 100644
--- a/security/selinux/ss/conditional.c
+++ b/security/selinux/ss/conditional.c
@@ -600,3 +600,158 @@ void cond_compute_av(struct avtab *ctab, struct avtab_key *key,
services_compute_xperms_drivers(xperms, node);
}
}
+
+static int cond_dup_av_list(struct cond_av_list *new,
+ struct cond_av_list *orig,
+ struct avtab *avtab)
+{
+ struct avtab_node *avnode;
+ u32 i;
+
+ memset(new, 0, sizeof(*new));
+
+ new->nodes = kcalloc(orig->len, sizeof(*new->nodes), GFP_KERNEL);
+ if (!new->nodes)
+ return -ENOMEM;
+
+ for (i = 0; i < orig->len; i++) {
+ avnode = avtab_search_node(avtab, &orig->nodes[i]->key);
+ if (WARN_ON(!avnode))
+ return -EINVAL;
+ new->nodes[i] = avnode;
+ new->len++;
+ }
+
+ return 0;
+}
+
+static int duplicate_policydb_cond_list(struct policydb *newp,
+ struct policydb *origp)
+{
+ int rc, i, j;
+
+ rc = avtab_duplicate(&newp->te_cond_avtab, &origp->te_cond_avtab);
+ if (rc)
+ return rc;
+
+ newp->cond_list_len = 0;
+ newp->cond_list = kcalloc(origp->cond_list_len,
+ sizeof(*newp->cond_list),
+ GFP_KERNEL);
+ if (!newp->cond_list)
+ goto error;
+
+ for (i = 0; i < origp->cond_list_len; i++) {
+ struct cond_node *newn = &newp->cond_list[i];
+ struct cond_node *orign = &origp->cond_list[i];
+
+ newp->cond_list_len++;
+
+ newn->cur_state = orign->cur_state;
+ newn->expr.nodes = kcalloc(orign->expr.len,
+ sizeof(*newn->expr.nodes), GFP_KERNEL);
+ if (!newn->expr.nodes)
+ goto error;
+ for (j = 0; j < orign->expr.len; j++)
+ newn->expr.nodes[j] = orign->expr.nodes[j];
+ newn->expr.len = orign->expr.len;
+
+ rc = cond_dup_av_list(&newn->true_list, &orign->true_list,
+ &newp->te_cond_avtab);
+ if (rc)
+ goto error;
+
+ rc = cond_dup_av_list(&newn->false_list, &orign->false_list,
+ &newp->te_cond_avtab);
+ if (rc)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ avtab_destroy(&newp->te_cond_avtab);
+ cond_list_destroy(newp);
+ return -ENOMEM;
+}
+
+static int cond_bools_destroy(void *key, void *datum, void *args)
+{
+ /* key was not copied so no need to free here */
+ kfree(datum);
+ return 0;
+}
+
+static int cond_bools_copy(struct hashtab_node *new, struct hashtab_node *orig, void *args)
+{
+ struct cond_bool_datum *datum;
+
+ datum = kmemdup(orig->datum, sizeof(struct cond_bool_datum),
+ GFP_KERNEL);
+ if (!datum)
+ return -ENOMEM;
+
+ new->key = orig->key; /* No need to copy, never modified */
+ new->datum = datum;
+ return 0;
+}
+
+static int cond_bools_index(void *key, void *datum, void *args)
+{
+ struct cond_bool_datum *booldatum, **cond_bool_array;
+
+ booldatum = datum;
+ cond_bool_array = args;
+ cond_bool_array[booldatum->value - 1] = booldatum;
+
+ return 0;
+}
+
+static int duplicate_policydb_bools(struct policydb *newdb,
+ struct policydb *orig)
+{
+ struct cond_bool_datum **cond_bool_array;
+ int rc;
+
+ cond_bool_array = kmalloc_array(orig->p_bools.nprim,
+ sizeof(*orig->bool_val_to_struct),
+ GFP_KERNEL);
+ if (!cond_bool_array)
+ return -ENOMEM;
+
+ rc = hashtab_duplicate(&newdb->p_bools.table, &orig->p_bools.table,
+ cond_bools_copy, cond_bools_destroy, NULL);
+ if (rc) {
+ kfree(cond_bool_array);
+ return -ENOMEM;
+ }
+
+ hashtab_map(&newdb->p_bools.table, cond_bools_index, cond_bool_array);
+ newdb->bool_val_to_struct = cond_bool_array;
+
+ newdb->p_bools.nprim = orig->p_bools.nprim;
+
+ return 0;
+}
+
+void cond_policydb_destroy_dup(struct policydb *p)
+{
+ hashtab_map(&p->p_bools.table, cond_bools_destroy, NULL);
+ hashtab_destroy(&p->p_bools.table);
+ cond_policydb_destroy(p);
+}
+
+int cond_policydb_dup(struct policydb *new, struct policydb *orig)
+{
+ cond_policydb_init(new);
+
+ if (duplicate_policydb_bools(new, orig))
+ return -ENOMEM;
+
+ if (duplicate_policydb_cond_list(new, orig)) {
+ cond_policydb_destroy_dup(new);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
diff --git a/security/selinux/ss/conditional.h b/security/selinux/ss/conditional.h
index 79e7e03db859..e47ec6ddeaf6 100644
--- a/security/selinux/ss/conditional.h
+++ b/security/selinux/ss/conditional.h
@@ -79,5 +79,7 @@ void cond_compute_av(struct avtab *ctab, struct avtab_key *key,
void cond_compute_xperms(struct avtab *ctab, struct avtab_key *key,
struct extended_perms_decision *xpermd);
void evaluate_cond_nodes(struct policydb *p);
+void cond_policydb_destroy_dup(struct policydb *p);
+int cond_policydb_dup(struct policydb *new, struct policydb *orig);
#endif /* _CONDITIONAL_H_ */
diff --git a/security/selinux/ss/hashtab.c b/security/selinux/ss/hashtab.c
index d9287bb4bfeb..dab8c25c739b 100644
--- a/security/selinux/ss/hashtab.c
+++ b/security/selinux/ss/hashtab.c
@@ -122,6 +122,59 @@ void hashtab_stat(struct hashtab *h, struct hashtab_info *info)
info->max_chain_len = max_chain_len;
}
+int hashtab_duplicate(struct hashtab *new, struct hashtab *orig,
+ int (*copy)(struct hashtab_node *new,
+ struct hashtab_node *orig, void *args),
+ int (*destroy)(void *k, void *d, void *args),
+ void *args)
+{
+ struct hashtab_node *cur, *tmp, *tail;
+ int i, rc;
+
+ memset(new, 0, sizeof(*new));
+
+ new->htable = kcalloc(orig->size, sizeof(*new->htable), GFP_KERNEL);
+ if (!new->htable)
+ return -ENOMEM;
+
+ new->size = orig->size;
+
+ for (i = 0; i < orig->size; i++) {
+ tail = NULL;
+ for (cur = orig->htable[i]; cur; cur = cur->next) {
+ tmp = kmem_cache_zalloc(hashtab_node_cachep,
+ GFP_KERNEL);
+ if (!tmp)
+ goto error;
+ rc = copy(tmp, cur, args);
+ if (rc) {
+ kmem_cache_free(hashtab_node_cachep, tmp);
+ goto error;
+ }
+ tmp->next = NULL;
+ if (!tail)
+ new->htable[i] = tmp;
+ else
+ tail->next = tmp;
+ tail = tmp;
+ new->nel++;
+ }
+ }
+
+ return 0;
+
+ error:
+ for (i = 0; i < new->size; i++) {
+ for (cur = new->htable[i]; cur; cur = tmp) {
+ tmp = cur->next;
+ destroy(cur->key, cur->datum, args);
+ kmem_cache_free(hashtab_node_cachep, cur);
+ }
+ }
+ kmem_cache_free(hashtab_node_cachep, new);
+ return -ENOMEM;
+}
+
void __init hashtab_cache_init(void)
{
hashtab_node_cachep = kmem_cache_create("hashtab_node",
diff --git a/security/selinux/ss/hashtab.h b/security/selinux/ss/hashtab.h
index 3c952f0f01f9..043a773bf0b7 100644
--- a/security/selinux/ss/hashtab.h
+++ b/security/selinux/ss/hashtab.h
@@ -136,6 +136,12 @@ int hashtab_map(struct hashtab *h,
int (*apply)(void *k, void *d, void *args),
void *args);
+int hashtab_duplicate(struct hashtab *new, struct hashtab *orig,
+ int (*copy)(struct hashtab_node *new,
+ struct hashtab_node *orig, void *args),
+ int (*destroy)(void *k, void *d, void *args),
+ void *args);
+
/* Fill info with some hash table statistics */
void hashtab_stat(struct hashtab *h, struct hashtab_info *info);
diff --git a/security/selinux/ss/services.c b/security/selinux/ss/services.c
index 1caf4e603309..9704c8a32303 100644
--- a/security/selinux/ss/services.c
+++ b/security/selinux/ss/services.c
@@ -64,25 +64,7 @@
#include "xfrm.h"
#include "ebitmap.h"
#include "audit.h"
-
-/* Policy capability names */
-const char *selinux_policycap_names[__POLICYDB_CAPABILITY_MAX] = {
- "network_peer_controls",
- "open_perms",
- "extended_socket_class",
- "always_check_network",
- "cgroup_seclabel",
- "nnp_nosuid_transition",
- "genfs_seclabel_symlinks"
-};
-
-static struct selinux_ss selinux_ss;
-
-void selinux_ss_init(struct selinux_ss **ss)
-{
- rwlock_init(&selinux_ss.policy_rwlock);
- *ss = &selinux_ss;
-}
+#include "policycap_names.h"
/* Forward declaration. */
static int context_struct_to_string(struct policydb *policydb,
@@ -248,9 +230,17 @@ static void map_decision(struct selinux_map *map,
int security_mls_enabled(struct selinux_state *state)
{
- struct policydb *p = &state->ss->policydb;
+ int mls_enabled;
+ struct selinux_policy *policy;
- return p->mls_enabled;
+ if (!selinux_initialized(state))
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ mls_enabled = policy->policydb.mls_enabled;
+ rcu_read_unlock();
+ return mls_enabled;
}
/*
@@ -721,13 +711,14 @@ static void context_struct_compute_av(struct policydb *policydb,
}
static int security_validtrans_handle_fail(struct selinux_state *state,
- 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;
+ struct selinux_policy *policy,
+ struct sidtab_entry *oentry,
+ struct sidtab_entry *nentry,
+ struct sidtab_entry *tentry,
+ u16 tclass)
+{
+ struct policydb *p = &policy->policydb;
+ struct sidtab *sidtab = policy->sidtab;
char *o = NULL, *n = NULL, *t = NULL;
u32 olen, nlen, tlen;
@@ -755,6 +746,7 @@ static int security_compute_validatetrans(struct selinux_state *state,
u32 oldsid, u32 newsid, u32 tasksid,
u16 orig_tclass, bool user)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct sidtab_entry *oentry;
@@ -769,13 +761,14 @@ static int security_compute_validatetrans(struct selinux_state *state,
if (!selinux_initialized(state))
return 0;
- read_lock(&state->ss->policy_rwlock);
+ rcu_read_lock();
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
if (!user)
- tclass = unmap_class(&state->ss->map, orig_tclass);
+ tclass = unmap_class(&policy->map, orig_tclass);
else
tclass = orig_tclass;
@@ -818,17 +811,18 @@ static int security_compute_validatetrans(struct selinux_state *state,
rc = -EPERM;
else
rc = security_validtrans_handle_fail(state,
- oentry,
- nentry,
- tentry,
- tclass);
+ policy,
+ oentry,
+ nentry,
+ tentry,
+ tclass);
goto out;
}
constraint = constraint->next;
}
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -860,6 +854,7 @@ int security_validate_transition(struct selinux_state *state,
int security_bounded_transition(struct selinux_state *state,
u32 old_sid, u32 new_sid)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct sidtab_entry *old_entry, *new_entry;
@@ -870,10 +865,10 @@ int security_bounded_transition(struct selinux_state *state,
if (!selinux_initialized(state))
return 0;
- read_lock(&state->ss->policy_rwlock);
-
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
rc = -EINVAL;
old_entry = sidtab_search_entry(sidtab, old_sid);
@@ -934,17 +929,20 @@ int security_bounded_transition(struct selinux_state *state,
kfree(old_name);
}
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
-static void avd_init(struct selinux_state *state, struct av_decision *avd)
+static void avd_init(struct selinux_policy *policy, struct av_decision *avd)
{
avd->allowed = 0;
avd->auditallow = 0;
avd->auditdeny = 0xffffffff;
- avd->seqno = state->ss->latest_granting;
+ if (policy)
+ avd->seqno = policy->latest_granting;
+ else
+ avd->seqno = 0;
avd->flags = 0;
}
@@ -1009,6 +1007,7 @@ void security_compute_xperms_decision(struct selinux_state *state,
u8 driver,
struct extended_perms_decision *xpermd)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
u16 tclass;
@@ -1025,12 +1024,13 @@ void security_compute_xperms_decision(struct selinux_state *state,
memset(xpermd->auditallow->p, 0, sizeof(xpermd->auditallow->p));
memset(xpermd->dontaudit->p, 0, sizeof(xpermd->dontaudit->p));
- read_lock(&state->ss->policy_rwlock);
+ rcu_read_lock();
if (!selinux_initialized(state))
goto allow;
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
scontext = sidtab_search(sidtab, ssid);
if (!scontext) {
@@ -1046,7 +1046,7 @@ void security_compute_xperms_decision(struct selinux_state *state,
goto out;
}
- tclass = unmap_class(&state->ss->map, orig_tclass);
+ tclass = unmap_class(&policy->map, orig_tclass);
if (unlikely(orig_tclass && !tclass)) {
if (policydb->allow_unknown)
goto allow;
@@ -1078,7 +1078,7 @@ void security_compute_xperms_decision(struct selinux_state *state,
}
}
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return;
allow:
memset(xpermd->allowed->p, 0xff, sizeof(xpermd->allowed->p));
@@ -1103,19 +1103,21 @@ void security_compute_av(struct selinux_state *state,
struct av_decision *avd,
struct extended_perms *xperms)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
u16 tclass;
struct context *scontext = NULL, *tcontext = NULL;
- read_lock(&state->ss->policy_rwlock);
- avd_init(state, avd);
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ avd_init(policy, avd);
xperms->len = 0;
if (!selinux_initialized(state))
goto allow;
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
scontext = sidtab_search(sidtab, ssid);
if (!scontext) {
@@ -1135,7 +1137,7 @@ void security_compute_av(struct selinux_state *state,
goto out;
}
- tclass = unmap_class(&state->ss->map, orig_tclass);
+ tclass = unmap_class(&policy->map, orig_tclass);
if (unlikely(orig_tclass && !tclass)) {
if (policydb->allow_unknown)
goto allow;
@@ -1143,10 +1145,10 @@ void security_compute_av(struct selinux_state *state,
}
context_struct_compute_av(policydb, scontext, tcontext, tclass, avd,
xperms);
- map_decision(&state->ss->map, orig_tclass, avd,
+ map_decision(&policy->map, orig_tclass, avd,
policydb->allow_unknown);
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return;
allow:
avd->allowed = 0xffffffff;
@@ -1159,17 +1161,19 @@ void security_compute_av_user(struct selinux_state *state,
u16 tclass,
struct av_decision *avd)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct context *scontext = NULL, *tcontext = NULL;
- read_lock(&state->ss->policy_rwlock);
- avd_init(state, avd);
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ avd_init(policy, avd);
if (!selinux_initialized(state))
goto allow;
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
scontext = sidtab_search(sidtab, ssid);
if (!scontext) {
@@ -1198,7 +1202,7 @@ void security_compute_av_user(struct selinux_state *state,
context_struct_compute_av(policydb, scontext, tcontext, tclass, avd,
NULL);
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return;
allow:
avd->allowed = 0xffffffff;
@@ -1283,6 +1287,7 @@ static int sidtab_entry_to_string(struct policydb *p,
int security_sidtab_hash_stats(struct selinux_state *state, char *page)
{
+ struct selinux_policy *policy;
int rc;
if (!selinux_initialized(state)) {
@@ -1291,9 +1296,10 @@ int security_sidtab_hash_stats(struct selinux_state *state, char *page)
return -EINVAL;
}
- read_lock(&state->ss->policy_rwlock);
- rc = sidtab_hash_stats(state->ss->sidtab, page);
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ rc = sidtab_hash_stats(policy->sidtab, page);
+ rcu_read_unlock();
return rc;
}
@@ -1310,6 +1316,7 @@ static int security_sid_to_context_core(struct selinux_state *state,
u32 *scontext_len, int force,
int only_invalid)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct sidtab_entry *entry;
@@ -1339,9 +1346,10 @@ static int security_sid_to_context_core(struct selinux_state *state,
"load_policy on unknown SID %d\n", __func__, sid);
return -EINVAL;
}
- read_lock(&state->ss->policy_rwlock);
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
if (force)
entry = sidtab_search_entry_force(sidtab, sid);
@@ -1360,7 +1368,7 @@ static int security_sid_to_context_core(struct selinux_state *state,
scontext_len);
out_unlock:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -1495,6 +1503,7 @@ static int security_context_to_sid_core(struct selinux_state *state,
u32 *sid, u32 def_sid, gfp_t gfp_flags,
int force)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
char *scontext2, *str = NULL;
@@ -1533,9 +1542,10 @@ static int security_context_to_sid_core(struct selinux_state *state,
if (!str)
goto out;
}
- read_lock(&state->ss->policy_rwlock);
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
rc = string_to_context_struct(policydb, sidtab, scontext2,
&context, def_sid);
if (rc == -EINVAL && force) {
@@ -1547,7 +1557,7 @@ static int security_context_to_sid_core(struct selinux_state *state,
rc = sidtab_context_to_sid(sidtab, &context, sid);
context_destroy(&context);
out_unlock:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
out:
kfree(scontext2);
kfree(str);
@@ -1617,13 +1627,14 @@ int security_context_to_sid_force(struct selinux_state *state,
static int compute_sid_handle_invalid_context(
struct selinux_state *state,
+ struct selinux_policy *policy,
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;
+ struct policydb *policydb = &policy->policydb;
+ struct sidtab *sidtab = policy->sidtab;
char *s = NULL, *t = NULL, *n = NULL;
u32 slen, tlen, nlen;
struct audit_buffer *ab;
@@ -1690,6 +1701,7 @@ static int security_compute_sid(struct selinux_state *state,
u32 *out_sid,
bool kern)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct class_datum *cladatum = NULL;
@@ -1716,19 +1728,21 @@ static int security_compute_sid(struct selinux_state *state,
context_init(&newcontext);
- read_lock(&state->ss->policy_rwlock);
+ rcu_read_lock();
+
+ policy = rcu_dereference(state->policy);
if (kern) {
- tclass = unmap_class(&state->ss->map, orig_tclass);
+ tclass = unmap_class(&policy->map, orig_tclass);
sock = security_is_socket_class(orig_tclass);
} else {
tclass = orig_tclass;
- sock = security_is_socket_class(map_class(&state->ss->map,
+ sock = security_is_socket_class(map_class(&policy->map,
tclass));
}
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
sentry = sidtab_search_entry(sidtab, ssid);
if (!sentry) {
@@ -1848,15 +1862,16 @@ 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, sentry, tentry,
- tclass, &newcontext);
+ rc = compute_sid_handle_invalid_context(state, policy, sentry,
+ tentry, tclass,
+ &newcontext);
if (rc)
goto out_unlock;
}
/* Obtain the sid for the context. */
rc = sidtab_context_to_sid(sidtab, &newcontext, out_sid);
out_unlock:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
context_destroy(&newcontext);
out:
return rc;
@@ -1943,9 +1958,9 @@ int security_change_sid(struct selinux_state *state,
static inline int convert_context_handle_invalid_context(
struct selinux_state *state,
+ struct policydb *policydb,
struct context *context)
{
- struct policydb *policydb = &state->ss->policydb;
char *s;
u32 len;
@@ -2077,7 +2092,9 @@ static int convert_context(struct context *oldc, struct context *newc, void *p)
/* Check the validity of the new context. */
if (!policydb_context_isvalid(args->newp, newc)) {
- rc = convert_context_handle_invalid_context(args->state, oldc);
+ rc = convert_context_handle_invalid_context(args->state,
+ args->oldp,
+ oldc);
if (rc)
goto bad;
}
@@ -2096,14 +2113,18 @@ bad:
return 0;
}
-static void security_load_policycaps(struct selinux_state *state)
+static void security_load_policycaps(struct selinux_state *state,
+ struct selinux_policy *policy)
{
- struct policydb *p = &state->ss->policydb;
+ struct policydb *p;
unsigned int i;
struct ebitmap_node *node;
+ p = &policy->policydb;
+
for (i = 0; i < ARRAY_SIZE(state->policycap); i++)
- state->policycap[i] = ebitmap_get_bit(&p->policycaps, i);
+ WRITE_ONCE(state->policycap[i],
+ ebitmap_get_bit(&p->policycaps, i));
for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++)
pr_info("SELinux: policy capability %s=%d\n",
@@ -2117,8 +2138,97 @@ static void security_load_policycaps(struct selinux_state *state)
}
}
-static int security_preserve_bools(struct selinux_state *state,
- struct policydb *newpolicydb);
+static int security_preserve_bools(struct selinux_policy *oldpolicy,
+ struct selinux_policy *newpolicy);
+
+static void selinux_policy_free(struct selinux_policy *policy)
+{
+ if (!policy)
+ return;
+
+ sidtab_destroy(policy->sidtab);
+ kfree(policy->map.mapping);
+ policydb_destroy(&policy->policydb);
+ kfree(policy->sidtab);
+ kfree(policy);
+}
+
+static void selinux_policy_cond_free(struct selinux_policy *policy)
+{
+ cond_policydb_destroy_dup(&policy->policydb);
+ kfree(policy);
+}
+
+void selinux_policy_cancel(struct selinux_state *state,
+ struct selinux_policy *policy)
+{
+ struct selinux_policy *oldpolicy;
+
+ oldpolicy = rcu_dereference_protected(state->policy,
+ lockdep_is_held(&state->policy_mutex));
+
+ sidtab_cancel_convert(oldpolicy->sidtab);
+ selinux_policy_free(policy);
+}
+
+static void selinux_notify_policy_change(struct selinux_state *state,
+ u32 seqno)
+{
+ /* Flush external caches and notify userspace of policy load */
+ avc_ss_reset(state->avc, seqno);
+ selnl_notify_policyload(seqno);
+ selinux_status_update_policyload(state, seqno);
+ selinux_netlbl_cache_invalidate();
+ selinux_xfrm_notify_policyload();
+}
+
+void selinux_policy_commit(struct selinux_state *state,
+ struct selinux_policy *newpolicy)
+{
+ struct selinux_policy *oldpolicy;
+ u32 seqno;
+
+ oldpolicy = rcu_dereference_protected(state->policy,
+ lockdep_is_held(&state->policy_mutex));
+
+ /* If switching between different policy types, log MLS status */
+ if (oldpolicy) {
+ if (oldpolicy->policydb.mls_enabled && !newpolicy->policydb.mls_enabled)
+ pr_info("SELinux: Disabling MLS support...\n");
+ else if (!oldpolicy->policydb.mls_enabled && newpolicy->policydb.mls_enabled)
+ pr_info("SELinux: Enabling MLS support...\n");
+ }
+
+ /* Set latest granting seqno for new policy. */
+ if (oldpolicy)
+ newpolicy->latest_granting = oldpolicy->latest_granting + 1;
+ else
+ newpolicy->latest_granting = 1;
+ seqno = newpolicy->latest_granting;
+
+ /* Install the new policy. */
+ rcu_assign_pointer(state->policy, newpolicy);
+
+ /* Load the policycaps from the new policy */
+ security_load_policycaps(state, newpolicy);
+
+ if (!selinux_initialized(state)) {
+ /*
+ * After first policy load, the security server is
+ * marked as initialized and ready to handle requests and
+ * any objects created prior to policy load are then labeled.
+ */
+ selinux_mark_initialized(state);
+ selinux_complete_init();
+ }
+
+ /* Free the old policy */
+ synchronize_rcu();
+ selinux_policy_free(oldpolicy);
+
+ /* Notify others of the policy change */
+ selinux_notify_policy_change(state, seqno);
+}
/**
* security_load_policy - Load a security policy configuration.
@@ -2130,173 +2240,95 @@ static int security_preserve_bools(struct selinux_state *state,
* This function will flush the access vector cache after
* loading the new policy.
*/
-int security_load_policy(struct selinux_state *state, void *data, size_t len)
+int security_load_policy(struct selinux_state *state, void *data, size_t len,
+ struct selinux_policy **newpolicyp)
{
- struct policydb *policydb;
- struct sidtab *oldsidtab, *newsidtab;
- struct policydb *oldpolicydb, *newpolicydb;
- struct selinux_mapping *oldmapping;
- struct selinux_map newmap;
+ struct selinux_policy *newpolicy, *oldpolicy;
struct sidtab_convert_params convert_params;
struct convert_context_args args;
- u32 seqno;
int rc = 0;
struct policy_file file = { data, len }, *fp = &file;
- policydb = &state->ss->policydb;
-
- newsidtab = kmalloc(sizeof(*newsidtab), GFP_KERNEL);
- if (!newsidtab)
+ newpolicy = kzalloc(sizeof(*newpolicy), GFP_KERNEL);
+ if (!newpolicy)
return -ENOMEM;
- if (!selinux_initialized(state)) {
- rc = policydb_read(policydb, fp);
- if (rc) {
- kfree(newsidtab);
- return rc;
- }
-
- policydb->len = len;
- rc = selinux_set_mapping(policydb, secclass_map,
- &state->ss->map);
- if (rc) {
- kfree(newsidtab);
- policydb_destroy(policydb);
- return rc;
- }
-
- rc = policydb_load_isids(policydb, newsidtab);
- if (rc) {
- kfree(newsidtab);
- policydb_destroy(policydb);
- return rc;
- }
-
- state->ss->sidtab = newsidtab;
- security_load_policycaps(state);
- selinux_mark_initialized(state);
- seqno = ++state->ss->latest_granting;
- selinux_complete_init();
- avc_ss_reset(state->avc, seqno);
- selnl_notify_policyload(seqno);
- selinux_status_update_policyload(state, seqno);
- selinux_netlbl_cache_invalidate();
- selinux_xfrm_notify_policyload();
- return 0;
+ newpolicy->sidtab = kzalloc(sizeof(*newpolicy->sidtab), GFP_KERNEL);
+ if (!newpolicy->sidtab) {
+ rc = -ENOMEM;
+ goto err_policy;
}
- oldpolicydb = kcalloc(2, sizeof(*oldpolicydb), GFP_KERNEL);
- if (!oldpolicydb) {
- kfree(newsidtab);
- return -ENOMEM;
- }
- newpolicydb = oldpolicydb + 1;
+ rc = policydb_read(&newpolicy->policydb, fp);
+ if (rc)
+ goto err_sidtab;
+
+ newpolicy->policydb.len = len;
+ rc = selinux_set_mapping(&newpolicy->policydb, secclass_map,
+ &newpolicy->map);
+ if (rc)
+ goto err_policydb;
- rc = policydb_read(newpolicydb, fp);
+ rc = policydb_load_isids(&newpolicy->policydb, newpolicy->sidtab);
if (rc) {
- kfree(newsidtab);
- goto out;
+ pr_err("SELinux: unable to load the initial SIDs\n");
+ goto err_mapping;
}
- newpolicydb->len = len;
- /* If switching between different policy types, log MLS status */
- if (policydb->mls_enabled && !newpolicydb->mls_enabled)
- pr_info("SELinux: Disabling MLS support...\n");
- else if (!policydb->mls_enabled && newpolicydb->mls_enabled)
- pr_info("SELinux: Enabling MLS support...\n");
- rc = policydb_load_isids(newpolicydb, newsidtab);
- if (rc) {
- pr_err("SELinux: unable to load the initial SIDs\n");
- policydb_destroy(newpolicydb);
- kfree(newsidtab);
- goto out;
+ if (!selinux_initialized(state)) {
+ /* First policy load, so no need to preserve state from old policy */
+ *newpolicyp = newpolicy;
+ return 0;
}
- rc = selinux_set_mapping(newpolicydb, secclass_map, &newmap);
- if (rc)
- goto err;
+ oldpolicy = rcu_dereference_protected(state->policy,
+ lockdep_is_held(&state->policy_mutex));
- rc = security_preserve_bools(state, newpolicydb);
+ /* Preserve active boolean values from the old policy */
+ rc = security_preserve_bools(oldpolicy, newpolicy);
if (rc) {
pr_err("SELinux: unable to preserve booleans\n");
- goto err;
+ goto err_free_isids;
}
- oldsidtab = state->ss->sidtab;
-
/*
* Convert the internal representations of contexts
* in the new SID table.
*/
args.state = state;
- args.oldp = policydb;
- args.newp = newpolicydb;
+ args.oldp = &oldpolicy->policydb;
+ args.newp = &newpolicy->policydb;
convert_params.func = convert_context;
convert_params.args = &args;
- convert_params.target = newsidtab;
+ convert_params.target = newpolicy->sidtab;
- rc = sidtab_convert(oldsidtab, &convert_params);
+ rc = sidtab_convert(oldpolicy->sidtab, &convert_params);
if (rc) {
pr_err("SELinux: unable to convert the internal"
" representation of contexts in the new SID"
" table\n");
- goto err;
+ goto err_free_isids;
}
- /* Save the old policydb and SID table to free later. */
- memcpy(oldpolicydb, policydb, sizeof(*policydb));
-
- /* Install the new policydb and SID table. */
- write_lock_irq(&state->ss->policy_rwlock);
- memcpy(policydb, newpolicydb, sizeof(*policydb));
- state->ss->sidtab = newsidtab;
- security_load_policycaps(state);
- oldmapping = state->ss->map.mapping;
- state->ss->map.mapping = newmap.mapping;
- state->ss->map.size = newmap.size;
- seqno = ++state->ss->latest_granting;
- write_unlock_irq(&state->ss->policy_rwlock);
-
- /* Free the old policydb and SID table. */
- policydb_destroy(oldpolicydb);
- sidtab_destroy(oldsidtab);
- kfree(oldsidtab);
- kfree(oldmapping);
-
- avc_ss_reset(state->avc, seqno);
- selnl_notify_policyload(seqno);
- selinux_status_update_policyload(state, seqno);
- selinux_netlbl_cache_invalidate();
- selinux_xfrm_notify_policyload();
-
- rc = 0;
- goto out;
+ *newpolicyp = newpolicy;
+ return 0;
-err:
- kfree(newmap.mapping);
- sidtab_destroy(newsidtab);
- kfree(newsidtab);
- policydb_destroy(newpolicydb);
+err_free_isids:
+ sidtab_destroy(newpolicy->sidtab);
+err_mapping:
+ kfree(newpolicy->map.mapping);
+err_policydb:
+ policydb_destroy(&newpolicy->policydb);
+err_sidtab:
+ kfree(newpolicy->sidtab);
+err_policy:
+ kfree(newpolicy);
-out:
- kfree(oldpolicydb);
return rc;
}
-size_t security_policydb_len(struct selinux_state *state)
-{
- struct policydb *p = &state->ss->policydb;
- size_t len;
-
- read_lock(&state->ss->policy_rwlock);
- len = p->len;
- read_unlock(&state->ss->policy_rwlock);
-
- return len;
-}
-
/**
* security_port_sid - Obtain the SID for a port.
* @protocol: protocol number
@@ -2306,15 +2338,21 @@ size_t security_policydb_len(struct selinux_state *state)
int security_port_sid(struct selinux_state *state,
u8 protocol, u16 port, u32 *out_sid)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct ocontext *c;
int rc = 0;
- read_lock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state)) {
+ *out_sid = SECINITSID_PORT;
+ return 0;
+ }
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
c = policydb->ocontexts[OCON_PORT];
while (c) {
@@ -2338,7 +2376,7 @@ int security_port_sid(struct selinux_state *state,
}
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -2351,15 +2389,21 @@ out:
int security_ib_pkey_sid(struct selinux_state *state,
u64 subnet_prefix, u16 pkey_num, u32 *out_sid)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct ocontext *c;
int rc = 0;
- read_lock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state)) {
+ *out_sid = SECINITSID_UNLABELED;
+ return 0;
+ }
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
c = policydb->ocontexts[OCON_IBPKEY];
while (c) {
@@ -2384,7 +2428,7 @@ int security_ib_pkey_sid(struct selinux_state *state,
*out_sid = SECINITSID_UNLABELED;
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -2397,15 +2441,21 @@ out:
int security_ib_endport_sid(struct selinux_state *state,
const char *dev_name, u8 port_num, u32 *out_sid)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct ocontext *c;
int rc = 0;
- read_lock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state)) {
+ *out_sid = SECINITSID_UNLABELED;
+ return 0;
+ }
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
c = policydb->ocontexts[OCON_IBENDPORT];
while (c) {
@@ -2430,7 +2480,7 @@ int security_ib_endport_sid(struct selinux_state *state,
*out_sid = SECINITSID_UNLABELED;
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -2442,15 +2492,21 @@ out:
int security_netif_sid(struct selinux_state *state,
char *name, u32 *if_sid)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
int rc = 0;
struct ocontext *c;
- read_lock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state)) {
+ *if_sid = SECINITSID_NETIF;
+ return 0;
+ }
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
c = policydb->ocontexts[OCON_NETIF];
while (c) {
@@ -2475,7 +2531,7 @@ int security_netif_sid(struct selinux_state *state,
*if_sid = SECINITSID_NETIF;
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -2505,15 +2561,21 @@ int security_node_sid(struct selinux_state *state,
u32 addrlen,
u32 *out_sid)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
int rc;
struct ocontext *c;
- read_lock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state)) {
+ *out_sid = SECINITSID_NODE;
+ return 0;
+ }
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
switch (domain) {
case AF_INET: {
@@ -2568,7 +2630,7 @@ int security_node_sid(struct selinux_state *state,
rc = 0;
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -2594,6 +2656,7 @@ int security_get_user_sids(struct selinux_state *state,
u32 **sids,
u32 *nel)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
struct context *fromcon, usercon;
@@ -2610,10 +2673,10 @@ int security_get_user_sids(struct selinux_state *state,
if (!selinux_initialized(state))
goto out;
- read_lock(&state->ss->policy_rwlock);
-
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
context_init(&usercon);
@@ -2664,7 +2727,7 @@ int security_get_user_sids(struct selinux_state *state,
}
rc = 0;
out_unlock:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
if (rc || !mynel) {
kfree(mysids);
goto out;
@@ -2705,17 +2768,15 @@ out:
* Obtain a SID to use for a file in a filesystem that
* cannot support xattr or use a fixed labeling behavior like
* transition SIDs or task SIDs.
- *
- * The caller must acquire the policy_rwlock before calling this function.
*/
-static inline int __security_genfs_sid(struct selinux_state *state,
+static inline int __security_genfs_sid(struct selinux_policy *policy,
const char *fstype,
char *path,
u16 orig_sclass,
u32 *sid)
{
- struct policydb *policydb = &state->ss->policydb;
- struct sidtab *sidtab = state->ss->sidtab;
+ struct policydb *policydb = &policy->policydb;
+ struct sidtab *sidtab = policy->sidtab;
int len;
u16 sclass;
struct genfs *genfs;
@@ -2725,7 +2786,7 @@ static inline int __security_genfs_sid(struct selinux_state *state,
while (path[0] == '/' && path[1] == '/')
path++;
- sclass = unmap_class(&state->ss->map, orig_sclass);
+ sclass = unmap_class(&policy->map, orig_sclass);
*sid = SECINITSID_UNLABELED;
for (genfs = policydb->genfs; genfs; genfs = genfs->next) {
@@ -2777,20 +2838,39 @@ int security_genfs_sid(struct selinux_state *state,
u16 orig_sclass,
u32 *sid)
{
+ struct selinux_policy *policy;
int retval;
- read_lock(&state->ss->policy_rwlock);
- retval = __security_genfs_sid(state, fstype, path, orig_sclass, sid);
- read_unlock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state)) {
+ *sid = SECINITSID_UNLABELED;
+ return 0;
+ }
+
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ retval = __security_genfs_sid(policy,
+ fstype, path, orig_sclass, sid);
+ rcu_read_unlock();
return retval;
}
+int selinux_policy_genfs_sid(struct selinux_policy *policy,
+ const char *fstype,
+ char *path,
+ u16 orig_sclass,
+ u32 *sid)
+{
+ /* no lock required, policy is not yet accessible by other threads */
+ return __security_genfs_sid(policy, fstype, path, orig_sclass, sid);
+}
+
/**
* security_fs_use - Determine how to handle labeling for a filesystem.
* @sb: superblock in question
*/
int security_fs_use(struct selinux_state *state, struct super_block *sb)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
struct sidtab *sidtab;
int rc = 0;
@@ -2798,10 +2878,16 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
struct superblock_security_struct *sbsec = sb->s_security;
const char *fstype = sb->s_type->name;
- read_lock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state)) {
+ sbsec->behavior = SECURITY_FS_USE_NONE;
+ sbsec->sid = SECINITSID_UNLABELED;
+ return 0;
+ }
- policydb = &state->ss->policydb;
- sidtab = state->ss->sidtab;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
c = policydb->ocontexts[OCON_FSUSE];
while (c) {
@@ -2820,8 +2906,8 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
}
sbsec->sid = c->sid[0];
} else {
- rc = __security_genfs_sid(state, fstype, "/", SECCLASS_DIR,
- &sbsec->sid);
+ rc = __security_genfs_sid(policy, fstype, "/",
+ SECCLASS_DIR, &sbsec->sid);
if (rc) {
sbsec->behavior = SECURITY_FS_USE_NONE;
rc = 0;
@@ -2831,27 +2917,18 @@ int security_fs_use(struct selinux_state *state, struct super_block *sb)
}
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
-int security_get_bools(struct selinux_state *state,
+int security_get_bools(struct selinux_policy *policy,
u32 *len, char ***names, int **values)
{
struct policydb *policydb;
u32 i;
int rc;
- if (!selinux_initialized(state)) {
- *len = 0;
- *names = NULL;
- *values = NULL;
- return 0;
- }
-
- read_lock(&state->ss->policy_rwlock);
-
- policydb = &state->ss->policydb;
+ policydb = &policy->policydb;
*names = NULL;
*values = NULL;
@@ -2882,7 +2959,6 @@ int security_get_bools(struct selinux_state *state,
}
rc = 0;
out:
- read_unlock(&state->ss->policy_rwlock);
return rc;
err:
if (*names) {
@@ -2900,61 +2976,89 @@ err:
int security_set_bools(struct selinux_state *state, u32 len, int *values)
{
- struct policydb *policydb;
+ struct selinux_policy *newpolicy, *oldpolicy;
int rc;
- u32 i, lenp, seqno = 0;
+ u32 i, seqno = 0;
- write_lock_irq(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state))
+ return -EINVAL;
- policydb = &state->ss->policydb;
+ oldpolicy = rcu_dereference_protected(state->policy,
+ lockdep_is_held(&state->policy_mutex));
- rc = -EFAULT;
- lenp = policydb->p_bools.nprim;
- if (len != lenp)
- goto out;
+ /* Consistency check on number of booleans, should never fail */
+ if (WARN_ON(len != oldpolicy->policydb.p_bools.nprim))
+ return -EINVAL;
+
+ newpolicy = kmemdup(oldpolicy, sizeof(*newpolicy), GFP_KERNEL);
+ if (!newpolicy)
+ return -ENOMEM;
+
+ /*
+ * Deep copy only the parts of the policydb that might be
+ * modified as a result of changing booleans.
+ */
+ rc = cond_policydb_dup(&newpolicy->policydb, &oldpolicy->policydb);
+ if (rc) {
+ kfree(newpolicy);
+ return -ENOMEM;
+ }
+ /* Update the boolean states in the copy */
for (i = 0; i < len; i++) {
- if (!!values[i] != policydb->bool_val_to_struct[i]->state) {
+ int new_state = !!values[i];
+ int old_state = newpolicy->policydb.bool_val_to_struct[i]->state;
+
+ if (new_state != old_state) {
audit_log(audit_context(), GFP_ATOMIC,
AUDIT_MAC_CONFIG_CHANGE,
"bool=%s val=%d old_val=%d auid=%u ses=%u",
- sym_name(policydb, SYM_BOOLS, i),
- !!values[i],
- policydb->bool_val_to_struct[i]->state,
+ sym_name(&newpolicy->policydb, SYM_BOOLS, i),
+ new_state,
+ old_state,
from_kuid(&init_user_ns, audit_get_loginuid(current)),
audit_get_sessionid(current));
+ newpolicy->policydb.bool_val_to_struct[i]->state = new_state;
}
- if (values[i])
- policydb->bool_val_to_struct[i]->state = 1;
- else
- policydb->bool_val_to_struct[i]->state = 0;
}
- evaluate_cond_nodes(policydb);
+ /* Re-evaluate the conditional rules in the copy */
+ evaluate_cond_nodes(&newpolicy->policydb);
- seqno = ++state->ss->latest_granting;
- rc = 0;
-out:
- write_unlock_irq(&state->ss->policy_rwlock);
- if (!rc) {
- avc_ss_reset(state->avc, seqno);
- selnl_notify_policyload(seqno);
- selinux_status_update_policyload(state, seqno);
- selinux_xfrm_notify_policyload();
- }
- return rc;
+ /* Set latest granting seqno for new policy */
+ newpolicy->latest_granting = oldpolicy->latest_granting + 1;
+ seqno = newpolicy->latest_granting;
+
+ /* Install the new policy */
+ rcu_assign_pointer(state->policy, newpolicy);
+
+ /*
+ * Free the conditional portions of the old policydb
+ * that were copied for the new policy, and the oldpolicy
+ * structure itself but not what it references.
+ */
+ synchronize_rcu();
+ selinux_policy_cond_free(oldpolicy);
+
+ /* Notify others of the policy change */
+ selinux_notify_policy_change(state, seqno);
+ return 0;
}
int security_get_bool_value(struct selinux_state *state,
u32 index)
{
+ struct selinux_policy *policy;
struct policydb *policydb;
int rc;
u32 len;
- read_lock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state))
+ return 0;
- policydb = &state->ss->policydb;
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
rc = -EFAULT;
len = policydb->p_bools.nprim;
@@ -2963,27 +3067,28 @@ int security_get_bool_value(struct selinux_state *state,
rc = policydb->bool_val_to_struct[index]->state;
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
-static int security_preserve_bools(struct selinux_state *state,
- struct policydb *policydb)
+static int security_preserve_bools(struct selinux_policy *oldpolicy,
+ struct selinux_policy *newpolicy)
{
int rc, *bvalues = NULL;
char **bnames = NULL;
struct cond_bool_datum *booldatum;
u32 i, nbools = 0;
- rc = security_get_bools(state, &nbools, &bnames, &bvalues);
+ rc = security_get_bools(oldpolicy, &nbools, &bnames, &bvalues);
if (rc)
goto out;
for (i = 0; i < nbools; i++) {
- booldatum = symtab_search(&policydb->p_bools, bnames[i]);
+ booldatum = symtab_search(&newpolicy->policydb.p_bools,
+ bnames[i]);
if (booldatum)
booldatum->state = bvalues[i];
}
- evaluate_cond_nodes(policydb);
+ evaluate_cond_nodes(&newpolicy->policydb);
out:
if (bnames) {
@@ -3002,8 +3107,9 @@ out:
int security_sid_mls_copy(struct selinux_state *state,
u32 sid, u32 mls_sid, u32 *new_sid)
{
- struct policydb *policydb = &state->ss->policydb;
- struct sidtab *sidtab = state->ss->sidtab;
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
struct context *context1;
struct context *context2;
struct context newcon;
@@ -3012,14 +3118,22 @@ int security_sid_mls_copy(struct selinux_state *state,
int rc;
rc = 0;
- if (!selinux_initialized(state) || !policydb->mls_enabled) {
+ if (!selinux_initialized(state)) {
*new_sid = sid;
goto out;
}
context_init(&newcon);
- read_lock(&state->ss->policy_rwlock);
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
+ if (!policydb->mls_enabled) {
+ *new_sid = sid;
+ goto out_unlock;
+ }
rc = -EINVAL;
context1 = sidtab_search(sidtab, sid);
@@ -3046,7 +3160,8 @@ int security_sid_mls_copy(struct selinux_state *state,
/* Check the validity of the new context. */
if (!policydb_context_isvalid(policydb, &newcon)) {
- rc = convert_context_handle_invalid_context(state, &newcon);
+ rc = convert_context_handle_invalid_context(state, policydb,
+ &newcon);
if (rc) {
if (!context_struct_to_string(policydb, &newcon, &s,
&len)) {
@@ -3067,7 +3182,7 @@ int security_sid_mls_copy(struct selinux_state *state,
}
rc = sidtab_context_to_sid(sidtab, &newcon, new_sid);
out_unlock:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
context_destroy(&newcon);
out:
return rc;
@@ -3098,8 +3213,9 @@ int security_net_peersid_resolve(struct selinux_state *state,
u32 xfrm_sid,
u32 *peer_sid)
{
- struct policydb *policydb = &state->ss->policydb;
- struct sidtab *sidtab = state->ss->sidtab;
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
int rc;
struct context *nlbl_ctx;
struct context *xfrm_ctx;
@@ -3121,15 +3237,23 @@ int security_net_peersid_resolve(struct selinux_state *state,
return 0;
}
+ if (!selinux_initialized(state))
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
+
/*
* We don't need to check initialized here since the only way both
* nlbl_sid and xfrm_sid are not equal to SECSID_NULL would be if the
* security server was initialized and state->initialized was true.
*/
- if (!policydb->mls_enabled)
- return 0;
-
- read_lock(&state->ss->policy_rwlock);
+ if (!policydb->mls_enabled) {
+ rc = 0;
+ goto out;
+ }
rc = -EINVAL;
nlbl_ctx = sidtab_search(sidtab, nlbl_sid);
@@ -3156,7 +3280,7 @@ int security_net_peersid_resolve(struct selinux_state *state,
* expressive */
*peer_sid = xfrm_sid;
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -3173,19 +3297,13 @@ static int get_classes_callback(void *k, void *d, void *args)
return 0;
}
-int security_get_classes(struct selinux_state *state,
+int security_get_classes(struct selinux_policy *policy,
char ***classes, int *nclasses)
{
- struct policydb *policydb = &state->ss->policydb;
+ struct policydb *policydb;
int rc;
- if (!selinux_initialized(state)) {
- *nclasses = 0;
- *classes = NULL;
- return 0;
- }
-
- read_lock(&state->ss->policy_rwlock);
+ policydb = &policy->policydb;
rc = -ENOMEM;
*nclasses = policydb->p_classes.nprim;
@@ -3203,7 +3321,6 @@ int security_get_classes(struct selinux_state *state,
}
out:
- read_unlock(&state->ss->policy_rwlock);
return rc;
}
@@ -3220,14 +3337,14 @@ static int get_permissions_callback(void *k, void *d, void *args)
return 0;
}
-int security_get_permissions(struct selinux_state *state,
+int security_get_permissions(struct selinux_policy *policy,
char *class, char ***perms, int *nperms)
{
- struct policydb *policydb = &state->ss->policydb;
+ struct policydb *policydb;
int rc, i;
struct class_datum *match;
- read_lock(&state->ss->policy_rwlock);
+ policydb = &policy->policydb;
rc = -EINVAL;
match = symtab_search(&policydb->p_classes, class);
@@ -3256,11 +3373,9 @@ int security_get_permissions(struct selinux_state *state,
goto err;
out:
- read_unlock(&state->ss->policy_rwlock);
return rc;
err:
- read_unlock(&state->ss->policy_rwlock);
for (i = 0; i < *nperms; i++)
kfree((*perms)[i]);
kfree(*perms);
@@ -3269,12 +3384,32 @@ err:
int security_get_reject_unknown(struct selinux_state *state)
{
- return state->ss->policydb.reject_unknown;
+ struct selinux_policy *policy;
+ int value;
+
+ if (!selinux_initialized(state))
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ value = policy->policydb.reject_unknown;
+ rcu_read_unlock();
+ return value;
}
int security_get_allow_unknown(struct selinux_state *state)
{
- return state->ss->policydb.allow_unknown;
+ struct selinux_policy *policy;
+ int value;
+
+ if (!selinux_initialized(state))
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ value = policy->policydb.allow_unknown;
+ rcu_read_unlock();
+ return value;
}
/**
@@ -3290,12 +3425,16 @@ int security_get_allow_unknown(struct selinux_state *state)
int security_policycap_supported(struct selinux_state *state,
unsigned int req_cap)
{
- struct policydb *policydb = &state->ss->policydb;
+ struct selinux_policy *policy;
int rc;
- read_lock(&state->ss->policy_rwlock);
- rc = ebitmap_get_bit(&policydb->policycaps, req_cap);
- read_unlock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state))
+ return 0;
+
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ rc = ebitmap_get_bit(&policy->policydb.policycaps, req_cap);
+ rcu_read_unlock();
return rc;
}
@@ -3318,7 +3457,8 @@ void selinux_audit_rule_free(void *vrule)
int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule)
{
struct selinux_state *state = &selinux_state;
- struct policydb *policydb = &state->ss->policydb;
+ struct selinux_policy *policy;
+ struct policydb *policydb;
struct selinux_audit_rule *tmprule;
struct role_datum *roledatum;
struct type_datum *typedatum;
@@ -3361,9 +3501,11 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule)
context_init(&tmprule->au_ctxt);
- read_lock(&state->ss->policy_rwlock);
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
- tmprule->au_seqno = state->ss->latest_granting;
+ tmprule->au_seqno = policy->latest_granting;
switch (field) {
case AUDIT_SUBJ_USER:
@@ -3402,7 +3544,7 @@ int selinux_audit_rule_init(u32 field, u32 op, char *rulestr, void **vrule)
}
rc = 0;
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
if (rc) {
selinux_audit_rule_free(tmprule);
@@ -3442,6 +3584,7 @@ int selinux_audit_rule_known(struct audit_krule *rule)
int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule)
{
struct selinux_state *state = &selinux_state;
+ struct selinux_policy *policy;
struct context *ctxt;
struct mls_level *level;
struct selinux_audit_rule *rule = vrule;
@@ -3452,14 +3595,19 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule)
return -ENOENT;
}
- read_lock(&state->ss->policy_rwlock);
+ if (!selinux_initialized(state))
+ return 0;
+
+ rcu_read_lock();
- if (rule->au_seqno < state->ss->latest_granting) {
+ policy = rcu_dereference(state->policy);
+
+ if (rule->au_seqno < policy->latest_granting) {
match = -ESTALE;
goto out;
}
- ctxt = sidtab_search(state->ss->sidtab, sid);
+ ctxt = sidtab_search(policy->sidtab, sid);
if (unlikely(!ctxt)) {
WARN_ONCE(1, "selinux_audit_rule_match: unrecognized SID %d\n",
sid);
@@ -3543,7 +3691,7 @@ int selinux_audit_rule_match(u32 sid, u32 field, u32 op, void *vrule)
}
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return match;
}
@@ -3621,8 +3769,9 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state,
struct netlbl_lsm_secattr *secattr,
u32 *sid)
{
- struct policydb *policydb = &state->ss->policydb;
- struct sidtab *sidtab = state->ss->sidtab;
+ struct selinux_policy *policy;
+ struct policydb *policydb;
+ struct sidtab *sidtab;
int rc;
struct context *ctx;
struct context ctx_new;
@@ -3632,7 +3781,10 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state,
return 0;
}
- read_lock(&state->ss->policy_rwlock);
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
+ sidtab = policy->sidtab;
if (secattr->flags & NETLBL_SECATTR_CACHE)
*sid = *(u32 *)secattr->cache->data;
@@ -3668,12 +3820,12 @@ int security_netlbl_secattr_to_sid(struct selinux_state *state,
} else
*sid = SECSID_NULL;
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return 0;
out_free:
ebitmap_destroy(&ctx_new.range.level[0].cat);
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
@@ -3690,17 +3842,20 @@ out:
int security_netlbl_sid_to_secattr(struct selinux_state *state,
u32 sid, struct netlbl_lsm_secattr *secattr)
{
- struct policydb *policydb = &state->ss->policydb;
+ struct selinux_policy *policy;
+ struct policydb *policydb;
int rc;
struct context *ctx;
if (!selinux_initialized(state))
return 0;
- read_lock(&state->ss->policy_rwlock);
+ rcu_read_lock();
+ policy = rcu_dereference(state->policy);
+ policydb = &policy->policydb;
rc = -ENOENT;
- ctx = sidtab_search(state->ss->sidtab, sid);
+ ctx = sidtab_search(policy->sidtab, sid);
if (ctx == NULL)
goto out;
@@ -3715,7 +3870,7 @@ int security_netlbl_sid_to_secattr(struct selinux_state *state,
mls_export_netlbl_lvl(policydb, ctx, secattr);
rc = mls_export_netlbl_cat(policydb, ctx, secattr);
out:
- read_unlock(&state->ss->policy_rwlock);
+ rcu_read_unlock();
return rc;
}
#endif /* CONFIG_NETLABEL */
@@ -3729,15 +3884,16 @@ out:
int security_read_policy(struct selinux_state *state,
void **data, size_t *len)
{
- struct policydb *policydb = &state->ss->policydb;
+ struct selinux_policy *policy;
int rc;
struct policy_file fp;
- if (!selinux_initialized(state))
+ policy = rcu_dereference_protected(
+ state->policy, lockdep_is_held(&state->policy_mutex));
+ if (!policy)
return -EINVAL;
- *len = security_policydb_len(state);
-
+ *len = policy->policydb.len;
*data = vmalloc_user(*len);
if (!*data)
return -ENOMEM;
@@ -3745,10 +3901,7 @@ int security_read_policy(struct selinux_state *state,
fp.data = *data;
fp.len = *len;
- read_lock(&state->ss->policy_rwlock);
- rc = policydb_write(policydb, &fp);
- read_unlock(&state->ss->policy_rwlock);
-
+ rc = policydb_write(&policy->policydb, &fp);
if (rc)
return rc;
diff --git a/security/selinux/ss/services.h b/security/selinux/ss/services.h
index a06f3d835216..9555ad074303 100644
--- a/security/selinux/ss/services.h
+++ b/security/selinux/ss/services.h
@@ -22,12 +22,11 @@ struct selinux_map {
u16 size; /* array size of mapping */
};
-struct selinux_ss {
+struct selinux_policy {
struct sidtab *sidtab;
struct policydb policydb;
- rwlock_t policy_rwlock;
- u32 latest_granting;
struct selinux_map map;
+ u32 latest_granting;
} __randomize_layout;
void services_compute_xperms_drivers(struct extended_perms *xperms,
diff --git a/security/selinux/ss/sidtab.c b/security/selinux/ss/sidtab.c
index eb6d27b5aeb4..5ee190bd30f5 100644
--- a/security/selinux/ss/sidtab.c
+++ b/security/selinux/ss/sidtab.c
@@ -464,6 +464,16 @@ int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params)
return 0;
}
+void sidtab_cancel_convert(struct sidtab *s)
+{
+ unsigned long flags;
+
+ /* cancelling policy load - disable live convert of sidtab */
+ spin_lock_irqsave(&s->lock, flags);
+ s->convert = NULL;
+ spin_unlock_irqrestore(&s->lock, flags);
+}
+
static void sidtab_destroy_entry(struct sidtab_entry *entry)
{
context_destroy(&entry->context);
diff --git a/security/selinux/ss/sidtab.h b/security/selinux/ss/sidtab.h
index f2a84560b8b3..80c744d07ad6 100644
--- a/security/selinux/ss/sidtab.h
+++ b/security/selinux/ss/sidtab.h
@@ -123,6 +123,8 @@ static inline struct context *sidtab_search_force(struct sidtab *s, u32 sid)
int sidtab_convert(struct sidtab *s, struct sidtab_convert_params *params);
+void sidtab_cancel_convert(struct sidtab *s);
+
int sidtab_context_to_sid(struct sidtab *s, struct context *context, u32 *sid);
void sidtab_destroy(struct sidtab *s);