]> git.proxmox.com Git - mirror_ubuntu-jammy-kernel.git/commitdiff
Merge tag 'selinux-pr-20201012' of git://git.kernel.org/pub/scm/linux/kernel/git...
authorLinus Torvalds <torvalds@linux-foundation.org>
Tue, 13 Oct 2020 23:29:55 +0000 (16:29 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Tue, 13 Oct 2020 23:29:55 +0000 (16:29 -0700)
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
  ...

21 files changed:
Documentation/ABI/obsolete/sysfs-selinux-checkreqprot
MAINTAINERS
include/trace/events/avc.h [new file with mode: 0644]
scripts/selinux/mdp/mdp.c
security/selinux/avc.c
security/selinux/hooks.c
security/selinux/include/conditional.h
security/selinux/include/policycap.h [new file with mode: 0644]
security/selinux/include/policycap_names.h [new file with mode: 0644]
security/selinux/include/security.h
security/selinux/selinuxfs.c
security/selinux/ss/avtab.c
security/selinux/ss/avtab.h
security/selinux/ss/conditional.c
security/selinux/ss/conditional.h
security/selinux/ss/hashtab.c
security/selinux/ss/hashtab.h
security/selinux/ss/services.c
security/selinux/ss/services.h
security/selinux/ss/sidtab.c
security/selinux/ss/sidtab.h

index 49ed9c8fd1e56dc553399983b2af61684ac6414a..ed6b52ca210ff2e8ea476a71b30724de3588a498 100644 (file)
@@ -15,7 +15,7 @@ Description:
        actual protection), and Android and Linux distributions have been
        explicitly writing a "0" to /sys/fs/selinux/checkreqprot during
        initialization for some time.  Support for setting checkreqprot to 1
-       will be removed in a future kernel release, at which point the kernel
+       will be removed no sooner than June 2021, at which point the kernel
        will always cease using checkreqprot internally and will always
        check the actual protections being applied upon mmap/mprotect calls.
        The checkreqprot selinuxfs node will remain for backward compatibility
index 6da16c3707c605b838361d233f3cfb390114f347..6336f9314ae15bb18703c5339982077bf384e307 100644 (file)
@@ -15621,6 +15621,7 @@ T:      git git://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git
 F:     Documentation/ABI/obsolete/sysfs-selinux-checkreqprot
 F:     Documentation/ABI/obsolete/sysfs-selinux-disable
 F:     Documentation/admin-guide/LSM/SELinux.rst
+F:     include/trace/events/avc.h
 F:     include/uapi/linux/selinux_netlink.h
 F:     scripts/selinux/
 F:     security/selinux/
diff --git a/include/trace/events/avc.h b/include/trace/events/avc.h
new file mode 100644 (file)
index 0000000..b55fda2
--- /dev/null
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Authors:    ThiĆ©baud Weksteen <tweek@google.com>
+ *             Peter Enderborg <Peter.Enderborg@sony.com>
+ */
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM avc
+
+#if !defined(_TRACE_SELINUX_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_SELINUX_H
+
+#include <linux/tracepoint.h>
+
+TRACE_EVENT(selinux_audited,
+
+       TP_PROTO(struct selinux_audit_data *sad,
+               char *scontext,
+               char *tcontext,
+               const char *tclass
+       ),
+
+       TP_ARGS(sad, scontext, tcontext, tclass),
+
+       TP_STRUCT__entry(
+               __field(u32, requested)
+               __field(u32, denied)
+               __field(u32, audited)
+               __field(int, result)
+               __string(scontext, scontext)
+               __string(tcontext, tcontext)
+               __string(tclass, tclass)
+       ),
+
+       TP_fast_assign(
+               __entry->requested      = sad->requested;
+               __entry->denied         = sad->denied;
+               __entry->audited        = sad->audited;
+               __entry->result         = sad->result;
+               __assign_str(tcontext, tcontext);
+               __assign_str(scontext, scontext);
+               __assign_str(tclass, tclass);
+       ),
+
+       TP_printk("requested=0x%x denied=0x%x audited=0x%x result=%d scontext=%s tcontext=%s tclass=%s",
+               __entry->requested, __entry->denied, __entry->audited, __entry->result,
+               __get_str(scontext), __get_str(tcontext), __get_str(tclass)
+       )
+);
+
+#endif
+
+/* This part must be outside protection */
+#include <trace/define_trace.h>
index 6ceb88eb9b5901cc1264d6d03c0555fa47ad3bf4..105c1c31a316e1ff4d195151382034762f544363 100644 (file)
@@ -35,6 +35,9 @@ struct security_class_mapping {
 
 #include "classmap.h"
 #include "initial_sid_to_string.h"
+#include "policycap_names.h"
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
 
 int main(int argc, char *argv[])
 {
@@ -115,6 +118,10 @@ int main(int argc, char *argv[])
                }
        }
 
+       /* enable all policy capabilities */
+       for (i = 0; i < ARRAY_SIZE(selinux_policycap_names); i++)
+               fprintf(fout, "policycap %s;\n", selinux_policycap_names[i]);
+
        /* types, roles, and allows */
        fprintf(fout, "type base_t;\n");
        fprintf(fout, "role base_r;\n");
index d18cb32a242ae1c5c5391fa13b2ba73d8a7f44b1..3c05827608b6a8d5ccf3ec480047e2e5b8200bbf 100644 (file)
@@ -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);
index a340986aa92e1d9e2434f2aaf5c208930a8156cb..2dabd58b126a72d679c0130940a4b8f44f1f75c0 100644 (file)
@@ -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();
index 539ab357707d899b3294b00a363f3b56d81eb5e4..b09343346e3fe435d81e31283ff8dcbcef4bc134 100644 (file)
@@ -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 (file)
index 0000000..2ec038e
--- /dev/null
@@ -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 (file)
index 0000000..b89289f
--- /dev/null
@@ -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_ */
index b0e02cfe3ce14b8eac067ab88f9812e1eb156d3a..3cc8bab31ea85af65de5bfc2aa7bae68e860174a 100644 (file)
 #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,
index 4781314c251094068e9fac1284fbce3f45c26f65..4bde570d56a2ccd82b6c841df82ce8934d891f64 100644 (file)
@@ -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",
index 01b300a4a882d1dd5d428120e46d6e19b668fe70..0172d87e2b9aebeab88f455694b17eacef15e92f 100644 (file)
@@ -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;
index 5fdcb6696bcc070f9224e44177b5b587f5934678..4c4445ca9118e7ff4c3624ea9f958f650eb17de4 100644 (file)
@@ -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);
index 5a47258c1d77c30005f0f302634de161711109ce..0b32f3ab025e599753bbf5bdb00c4ac784885d7e 100644 (file)
@@ -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;
+}
index 79e7e03db859438031ecdacbb5029aac759d34de..e47ec6ddeaf616a2e665ab7732c3907debfcd101 100644 (file)
@@ -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_ */
index d9287bb4bfebbccbbbd4d628b39a23869ad99ab5..dab8c25c739b91e082156a5a47a73a852786b105 100644 (file)
@@ -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",
index 3c952f0f01f9c455011219ffefc2536f8ac03c69..043a773bf0b771c45679131e7ee2a1502752bd4f 100644 (file)
@@ -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);
 
index 1caf4e6033096772110497acb0b5ceaae1b857d2..9704c8a32303f250e72f5dfe187690669512349e 100644 (file)
 #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;
 
index a06f3d8352164499e11beef7b35eafe9f2109a3f..9555ad074303c9954a6ba9a327d07be3bbdb8d05 100644 (file)
@@ -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,
index eb6d27b5aeb45631057518f5329e6de5b2a5f1e3..5ee190bd30f53fde45c80d8a21f179371f8350df 100644 (file)
@@ -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);
index f2a84560b8b3cc12b0c7e4fd3137849f29cacfc5..80c744d07ad62280cdcb77939dfe3d81ef2d7790 100644 (file)
@@ -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);