summaryrefslogtreecommitdiffstats
path: root/security/apparmor/policy_compat.c
blob: 0cb02da8a3193fc2f5f4cb8f30e36af51dcdbee5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
// SPDX-License-Identifier: GPL-2.0-only
/*
 * AppArmor security module
 *
 * This file contains AppArmor functions for unpacking policy loaded
 * from userspace.
 *
 * Copyright (C) 1998-2008 Novell/SUSE
 * Copyright 2009-2022 Canonical Ltd.
 *
 * Code to provide backwards compatibility with older policy versions,
 * by converting/mapping older policy formats into the newer internal
 * formats.
 */

#include <linux/ctype.h>
#include <linux/errno.h>

#include "include/lib.h"
#include "include/policy_unpack.h"
#include "include/policy_compat.h"

/* remap old accept table embedded permissions to separate permission table */
static u32 dfa_map_xindex(u16 mask)
{
	u16 old_index = (mask >> 10) & 0xf;
	u32 index = 0;

	if (mask & 0x100)
		index |= AA_X_UNSAFE;
	if (mask & 0x200)
		index |= AA_X_INHERIT;
	if (mask & 0x80)
		index |= AA_X_UNCONFINED;

	if (old_index == 1) {
		index |= AA_X_UNCONFINED;
	} else if (old_index == 2) {
		index |= AA_X_NAME;
	} else if (old_index == 3) {
		index |= AA_X_NAME | AA_X_CHILD;
	} else if (old_index) {
		index |= AA_X_TABLE;
		index |= old_index - 4;
	}

	return index;
}

/*
 * map old dfa inline permissions to new format
 */
#define dfa_user_allow(dfa, state) (((ACCEPT_TABLE(dfa)[state]) & 0x7f) | \
				    ((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
#define dfa_user_xbits(dfa, state) (((ACCEPT_TABLE(dfa)[state]) >> 7) & 0x7f)
#define dfa_user_audit(dfa, state) ((ACCEPT_TABLE2(dfa)[state]) & 0x7f)
#define dfa_user_quiet(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 7) & 0x7f)
#define dfa_user_xindex(dfa, state) \
	(dfa_map_xindex(ACCEPT_TABLE(dfa)[state] & 0x3fff))

#define dfa_other_allow(dfa, state) ((((ACCEPT_TABLE(dfa)[state]) >> 14) & \
				      0x7f) |				\
				     ((ACCEPT_TABLE(dfa)[state]) & 0x80000000))
#define dfa_other_xbits(dfa, state) \
	((((ACCEPT_TABLE(dfa)[state]) >> 7) >> 14) & 0x7f)
#define dfa_other_audit(dfa, state) (((ACCEPT_TABLE2(dfa)[state]) >> 14) & 0x7f)
#define dfa_other_quiet(dfa, state) \
	((((ACCEPT_TABLE2(dfa)[state]) >> 7) >> 14) & 0x7f)
#define dfa_other_xindex(dfa, state) \
	dfa_map_xindex((ACCEPT_TABLE(dfa)[state] >> 14) & 0x3fff)

/**
 * map_old_perms - map old file perms layout to the new layout
 * @old: permission set in old mapping
 *
 * Returns: new permission mapping
 */
static u32 map_old_perms(u32 old)
{
	u32 new = old & 0xf;

	if (old & MAY_READ)
		new |= AA_MAY_GETATTR | AA_MAY_OPEN;
	if (old & MAY_WRITE)
		new |= AA_MAY_SETATTR | AA_MAY_CREATE | AA_MAY_DELETE |
		       AA_MAY_CHMOD | AA_MAY_CHOWN | AA_MAY_OPEN;
	if (old & 0x10)
		new |= AA_MAY_LINK;
	/* the old mapping lock and link_subset flags where overlaid
	 * and use was determined by part of a pair that they were in
	 */
	if (old & 0x20)
		new |= AA_MAY_LOCK | AA_LINK_SUBSET;
	if (old & 0x40)	/* AA_EXEC_MMAP */
		new |= AA_EXEC_MMAP;

	return new;
}

static void compute_fperms_allow(struct aa_perms *perms, struct aa_dfa *dfa,
				 aa_state_t state)
{
	perms->allow |= AA_MAY_GETATTR;

	/* change_profile wasn't determined by ownership in old mapping */
	if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
		perms->allow |= AA_MAY_CHANGE_PROFILE;
	if (ACCEPT_TABLE(dfa)[state] & 0x40000000)
		perms->allow |= AA_MAY_ONEXEC;
}

static struct aa_perms compute_fperms_user(struct aa_dfa *dfa,
					   aa_state_t state)
{
	struct aa_perms perms = { };

	perms.allow = map_old_perms(dfa_user_allow(dfa, state));
	perms.audit = map_old_perms(dfa_user_audit(dfa, state));
	perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
	perms.xindex = dfa_user_xindex(dfa, state);

	compute_fperms_allow(&perms, dfa, state);

	return perms;
}

static struct aa_perms compute_fperms_other(struct aa_dfa *dfa,
					    aa_state_t state)
{
	struct aa_perms perms = { };

	perms.allow = map_old_perms(dfa_other_allow(dfa, state));
	perms.audit = map_old_perms(dfa_other_audit(dfa, state));
	perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
	perms.xindex = dfa_other_xindex(dfa, state);

	compute_fperms_allow(&perms, dfa, state);

	return perms;
}

/**
 * compute_fperms - convert dfa compressed perms to internal perms and store
 *		    them so they can be retrieved later.
 * @dfa: a dfa using fperms to remap to internal permissions
 *
 * Returns: remapped perm table
 */
static struct aa_perms *compute_fperms(struct aa_dfa *dfa,
				       u32 *size)
{
	aa_state_t state;
	unsigned int state_count;
	struct aa_perms *table;

	AA_BUG(!dfa);

	state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
	/* DFAs are restricted from having a state_count of less than 2 */
	table = kvcalloc(state_count * 2, sizeof(struct aa_perms), GFP_KERNEL);
	if (!table)
		return NULL;
	*size = state_count * 2;

	for (state = 0; state < state_count; state++) {
		table[state * 2] = compute_fperms_user(dfa, state);
		table[state * 2 + 1] = compute_fperms_other(dfa, state);
	}

	return table;
}

static struct aa_perms *compute_xmatch_perms(struct aa_dfa *xmatch,
				      u32 *size)
{
	struct aa_perms *perms;
	int state;
	int state_count;

	AA_BUG(!xmatch);

	state_count = xmatch->tables[YYTD_ID_BASE]->td_lolen;
	/* DFAs are restricted from having a state_count of less than 2 */
	perms = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL);
	if (!perms)
		return NULL;
	*size = state_count;

	/* zero init so skip the trap state (state == 0) */
	for (state = 1; state < state_count; state++)
		perms[state].allow = dfa_user_allow(xmatch, state);

	return perms;
}

static u32 map_other(u32 x)
{
	return ((x & 0x3) << 8) |	/* SETATTR/GETATTR */
		((x & 0x1c) << 18) |	/* ACCEPT/BIND/LISTEN */
		((x & 0x60) << 19);	/* SETOPT/GETOPT */
}

static u32 map_xbits(u32 x)
{
	return ((x & 0x1) << 7) |
		((x & 0x7e) << 9);
}

static struct aa_perms compute_perms_entry(struct aa_dfa *dfa,
					   aa_state_t state,
					   u32 version)
{
	struct aa_perms perms = { };

	perms.allow = dfa_user_allow(dfa, state);
	perms.audit = dfa_user_audit(dfa, state);
	perms.quiet = dfa_user_quiet(dfa, state);

	/*
	 * This mapping is convulated due to history.
	 * v1-v4: only file perms, which are handled by compute_fperms
	 * v5: added policydb which dropped user conditional to gain new
	 *     perm bits, but had to map around the xbits because the
	 *     userspace compiler was still munging them.
	 * v9: adds using the xbits in policydb because the compiler now
	 *     supports treating policydb permission bits different.
	 *     Unfortunately there is no way to force auditing on the
	 *     perms represented by the xbits
	 */
	perms.allow |= map_other(dfa_other_allow(dfa, state));
	if (VERSION_LE(version, v8))
		perms.allow |= AA_MAY_LOCK;
	else
		perms.allow |= map_xbits(dfa_user_xbits(dfa, state));

	/*
	 * for v5-v9 perm mapping in the policydb, the other set is used
	 * to extend the general perm set
	 */
	perms.audit |= map_other(dfa_other_audit(dfa, state));
	perms.quiet |= map_other(dfa_other_quiet(dfa, state));
	if (VERSION_GT(version, v8))
		perms.quiet |= map_xbits(dfa_other_xbits(dfa, state));

	return perms;
}

static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version,
				      u32 *size)
{
	unsigned int state;
	unsigned int state_count;
	struct aa_perms *table;

	AA_BUG(!dfa);

	state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;
	/* DFAs are restricted from having a state_count of less than 2 */
	table = kvcalloc(state_count, sizeof(struct aa_perms), GFP_KERNEL);
	if (!table)
		return NULL;
	*size = state_count;

	/* zero init so skip the trap state (state == 0) */
	for (state = 1; state < state_count; state++)
		table[state] = compute_perms_entry(dfa, state, version);

	return table;
}

/**
 * remap_dfa_accept - remap old dfa accept table to be an index
 * @dfa: dfa to do the remapping on
 * @factor: scaling factor for the index conversion.
 *
 * Used in conjunction with compute_Xperms, it converts old style perms
 * that are encoded in the dfa accept tables to the new style where
 * there is a permission table and the accept table is an index into
 * the permission table.
 */
static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor)
{
	unsigned int state;
	unsigned int state_count = dfa->tables[YYTD_ID_BASE]->td_lolen;

	AA_BUG(!dfa);

	for (state = 0; state < state_count; state++)
		ACCEPT_TABLE(dfa)[state] = state * factor;
	kvfree(dfa->tables[YYTD_ID_ACCEPT2]);
	dfa->tables[YYTD_ID_ACCEPT2] = NULL;
}

/* TODO: merge different dfa mappings into single map_policy fn */
int aa_compat_map_xmatch(struct aa_policydb *policy)
{
	policy->perms = compute_xmatch_perms(policy->dfa, &policy->size);
	if (!policy->perms)
		return -ENOMEM;

	remap_dfa_accept(policy->dfa, 1);

	return 0;
}

int aa_compat_map_policy(struct aa_policydb *policy, u32 version)
{
	policy->perms = compute_perms(policy->dfa, version, &policy->size);
	if (!policy->perms)
		return -ENOMEM;

	remap_dfa_accept(policy->dfa, 1);

	return 0;
}

int aa_compat_map_file(struct aa_policydb *policy)
{
	policy->perms = compute_fperms(policy->dfa, &policy->size);
	if (!policy->perms)
		return -ENOMEM;

	remap_dfa_accept(policy->dfa, 2);

	return 0;
}