Skip to content

Commit 6406887

Browse files
WOnder93pcmoore
authored andcommitted
selinux: fix variable scope issue in live sidtab conversion
Commit 02a52c5 ("selinux: move policy commit after updating selinuxfs") moved the selinux_policy_commit() call out of security_load_policy() into sel_write_load(), which caused a subtle yet rather serious bug. The problem is that security_load_policy() passes a reference to the convert_params local variable to sidtab_convert(), which stores it in the sidtab, where it may be accessed until the policy is swapped over and RCU synchronized. Before 02a52c5, selinux_policy_commit() was called directly from security_load_policy(), so the convert_params pointer remained valid all the way until the old sidtab was destroyed, but now that's no longer the case and calls to sidtab_context_to_sid() on the old sidtab after security_load_policy() returns may cause invalid memory accesses. This can be easily triggered using the stress test from commit ee1a84f ("selinux: overhaul sidtab to fix bug and improve performance"): ``` function rand_cat() { echo $(( $RANDOM % 1024 )) } function do_work() { while true; do echo -n "system_u:system_r:kernel_t:s0:c$(rand_cat),c$(rand_cat)" \ >/sys/fs/selinux/context 2>/dev/null || true done } do_work >/dev/null & do_work >/dev/null & do_work >/dev/null & while load_policy; do echo -n .; sleep 0.1; done kill %1 kill %2 kill %3 ``` Fix this by allocating the temporary sidtab convert structures dynamically and passing them among the selinux_policy_{load,cancel,commit} functions. Fixes: 02a52c5 ("selinux: move policy commit after updating selinuxfs") Cc: [email protected] Tested-by: Tyler Hicks <[email protected]> Reviewed-by: Tyler Hicks <[email protected]> Signed-off-by: Ondrej Mosnacek <[email protected]> [PM: merge fuzz in security.h and services.c] Signed-off-by: Paul Moore <[email protected]>
1 parent 519dad3 commit 6406887

File tree

3 files changed

+55
-33
lines changed

3 files changed

+55
-33
lines changed

security/selinux/include/security.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,14 +219,21 @@ static inline bool selinux_policycap_genfs_seclabel_symlinks(void)
219219
return READ_ONCE(state->policycap[POLICYDB_CAPABILITY_GENFS_SECLABEL_SYMLINKS]);
220220
}
221221

222+
struct selinux_policy_convert_data;
223+
224+
struct selinux_load_state {
225+
struct selinux_policy *policy;
226+
struct selinux_policy_convert_data *convert_data;
227+
};
228+
222229
int security_mls_enabled(struct selinux_state *state);
223230
int security_load_policy(struct selinux_state *state,
224-
void *data, size_t len,
225-
struct selinux_policy **newpolicyp);
231+
void *data, size_t len,
232+
struct selinux_load_state *load_state);
226233
void selinux_policy_commit(struct selinux_state *state,
227-
struct selinux_policy *newpolicy);
234+
struct selinux_load_state *load_state);
228235
void selinux_policy_cancel(struct selinux_state *state,
229-
struct selinux_policy *policy);
236+
struct selinux_load_state *load_state);
230237
int security_read_policy(struct selinux_state *state,
231238
void **data, size_t *len);
232239

security/selinux/selinuxfs.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
616616

617617
{
618618
struct selinux_fs_info *fsi = file_inode(file)->i_sb->s_fs_info;
619-
struct selinux_policy *newpolicy;
619+
struct selinux_load_state load_state;
620620
ssize_t length;
621621
void *data = NULL;
622622

@@ -642,19 +642,19 @@ static ssize_t sel_write_load(struct file *file, const char __user *buf,
642642
if (copy_from_user(data, buf, count) != 0)
643643
goto out;
644644

645-
length = security_load_policy(fsi->state, data, count, &newpolicy);
645+
length = security_load_policy(fsi->state, data, count, &load_state);
646646
if (length) {
647647
pr_warn_ratelimited("SELinux: failed to load policy\n");
648648
goto out;
649649
}
650650

651-
length = sel_make_policy_nodes(fsi, newpolicy);
651+
length = sel_make_policy_nodes(fsi, load_state.policy);
652652
if (length) {
653-
selinux_policy_cancel(fsi->state, newpolicy);
653+
selinux_policy_cancel(fsi->state, &load_state);
654654
goto out;
655655
}
656656

657-
selinux_policy_commit(fsi->state, newpolicy);
657+
selinux_policy_commit(fsi->state, &load_state);
658658

659659
length = count;
660660

security/selinux/ss/services.c

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@
6666
#include "audit.h"
6767
#include "policycap_names.h"
6868

69+
struct convert_context_args {
70+
struct selinux_state *state;
71+
struct policydb *oldp;
72+
struct policydb *newp;
73+
};
74+
75+
struct selinux_policy_convert_data {
76+
struct convert_context_args args;
77+
struct sidtab_convert_params sidtab_params;
78+
};
79+
6980
/* Forward declaration. */
7081
static int context_struct_to_string(struct policydb *policydb,
7182
struct context *context,
@@ -1973,12 +1984,6 @@ static inline int convert_context_handle_invalid_context(
19731984
return 0;
19741985
}
19751986

1976-
struct convert_context_args {
1977-
struct selinux_state *state;
1978-
struct policydb *oldp;
1979-
struct policydb *newp;
1980-
};
1981-
19821987
/*
19831988
* Convert the values in the security context
19841989
* structure `oldc' from the values specified
@@ -2158,15 +2163,16 @@ static void selinux_policy_cond_free(struct selinux_policy *policy)
21582163
}
21592164

21602165
void selinux_policy_cancel(struct selinux_state *state,
2161-
struct selinux_policy *policy)
2166+
struct selinux_load_state *load_state)
21622167
{
21632168
struct selinux_policy *oldpolicy;
21642169

21652170
oldpolicy = rcu_dereference_protected(state->policy,
21662171
lockdep_is_held(&state->policy_mutex));
21672172

21682173
sidtab_cancel_convert(oldpolicy->sidtab);
2169-
selinux_policy_free(policy);
2174+
selinux_policy_free(load_state->policy);
2175+
kfree(load_state->convert_data);
21702176
}
21712177

21722178
static void selinux_notify_policy_change(struct selinux_state *state,
@@ -2181,9 +2187,9 @@ static void selinux_notify_policy_change(struct selinux_state *state,
21812187
}
21822188

21832189
void selinux_policy_commit(struct selinux_state *state,
2184-
struct selinux_policy *newpolicy)
2190+
struct selinux_load_state *load_state)
21852191
{
2186-
struct selinux_policy *oldpolicy;
2192+
struct selinux_policy *oldpolicy, *newpolicy = load_state->policy;
21872193
u32 seqno;
21882194

21892195
oldpolicy = rcu_dereference_protected(state->policy,
@@ -2223,6 +2229,7 @@ void selinux_policy_commit(struct selinux_state *state,
22232229
/* Free the old policy */
22242230
synchronize_rcu();
22252231
selinux_policy_free(oldpolicy);
2232+
kfree(load_state->convert_data);
22262233

22272234
/* Notify others of the policy change */
22282235
selinux_notify_policy_change(state, seqno);
@@ -2239,11 +2246,10 @@ void selinux_policy_commit(struct selinux_state *state,
22392246
* loading the new policy.
22402247
*/
22412248
int security_load_policy(struct selinux_state *state, void *data, size_t len,
2242-
struct selinux_policy **newpolicyp)
2249+
struct selinux_load_state *load_state)
22432250
{
22442251
struct selinux_policy *newpolicy, *oldpolicy;
2245-
struct sidtab_convert_params convert_params;
2246-
struct convert_context_args args;
2252+
struct selinux_policy_convert_data *convert_data;
22472253
int rc = 0;
22482254
struct policy_file file = { data, len }, *fp = &file;
22492255

@@ -2273,10 +2279,10 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len,
22732279
goto err_mapping;
22742280
}
22752281

2276-
22772282
if (!selinux_initialized(state)) {
22782283
/* First policy load, so no need to preserve state from old policy */
2279-
*newpolicyp = newpolicy;
2284+
load_state->policy = newpolicy;
2285+
load_state->convert_data = NULL;
22802286
return 0;
22812287
}
22822288

@@ -2290,29 +2296,38 @@ int security_load_policy(struct selinux_state *state, void *data, size_t len,
22902296
goto err_free_isids;
22912297
}
22922298

2299+
convert_data = kmalloc(sizeof(*convert_data), GFP_KERNEL);
2300+
if (!convert_data) {
2301+
rc = -ENOMEM;
2302+
goto err_free_isids;
2303+
}
2304+
22932305
/*
22942306
* Convert the internal representations of contexts
22952307
* in the new SID table.
22962308
*/
2297-
args.state = state;
2298-
args.oldp = &oldpolicy->policydb;
2299-
args.newp = &newpolicy->policydb;
2309+
convert_data->args.state = state;
2310+
convert_data->args.oldp = &oldpolicy->policydb;
2311+
convert_data->args.newp = &newpolicy->policydb;
23002312

2301-
convert_params.func = convert_context;
2302-
convert_params.args = &args;
2303-
convert_params.target = newpolicy->sidtab;
2313+
convert_data->sidtab_params.func = convert_context;
2314+
convert_data->sidtab_params.args = &convert_data->args;
2315+
convert_data->sidtab_params.target = newpolicy->sidtab;
23042316

2305-
rc = sidtab_convert(oldpolicy->sidtab, &convert_params);
2317+
rc = sidtab_convert(oldpolicy->sidtab, &convert_data->sidtab_params);
23062318
if (rc) {
23072319
pr_err("SELinux: unable to convert the internal"
23082320
" representation of contexts in the new SID"
23092321
" table\n");
2310-
goto err_free_isids;
2322+
goto err_free_convert_data;
23112323
}
23122324

2313-
*newpolicyp = newpolicy;
2325+
load_state->policy = newpolicy;
2326+
load_state->convert_data = convert_data;
23142327
return 0;
23152328

2329+
err_free_convert_data:
2330+
kfree(convert_data);
23162331
err_free_isids:
23172332
sidtab_destroy(newpolicy->sidtab);
23182333
err_mapping:

0 commit comments

Comments
 (0)