Commit | Line | Data |
---|---|---|
b886d83c | 1 | // SPDX-License-Identifier: GPL-2.0-only |
736ec752 JJ |
2 | /* |
3 | * AppArmor security module | |
4 | * | |
5 | * This file contains AppArmor functions for unpacking policy loaded from | |
6 | * userspace. | |
7 | * | |
8 | * Copyright (C) 1998-2008 Novell/SUSE | |
9 | * Copyright 2009-2010 Canonical Ltd. | |
10 | * | |
d410fa4e | 11 | * AppArmor uses a serialized binary format for loading policy. To find |
26fccd9e | 12 | * policy format documentation see Documentation/admin-guide/LSM/apparmor.rst |
736ec752 JJ |
13 | * All policy is validated before it is used. |
14 | */ | |
15 | ||
16 | #include <asm/unaligned.h> | |
b11e51dd | 17 | #include <kunit/visibility.h> |
736ec752 JJ |
18 | #include <linux/ctype.h> |
19 | #include <linux/errno.h> | |
f4d6b94b | 20 | #include <linux/zstd.h> |
736ec752 JJ |
21 | |
22 | #include "include/apparmor.h" | |
23 | #include "include/audit.h" | |
d8889d49 | 24 | #include "include/cred.h" |
f8eb8a13 | 25 | #include "include/crypto.h" |
408d53e9 | 26 | #include "include/file.h" |
736ec752 | 27 | #include "include/match.h" |
637f688d | 28 | #include "include/path.h" |
736ec752 JJ |
29 | #include "include/policy.h" |
30 | #include "include/policy_unpack.h" | |
caa9f579 | 31 | #include "include/policy_compat.h" |
5ebfb128 | 32 | |
736ec752 JJ |
33 | /* audit callback for unpack fields */ |
34 | static void audit_cb(struct audit_buffer *ab, void *va) | |
35 | { | |
36 | struct common_audit_data *sa = va; | |
ef88a7ac JJ |
37 | |
38 | if (aad(sa)->iface.ns) { | |
39 | audit_log_format(ab, " ns="); | |
40 | audit_log_untrustedstring(ab, aad(sa)->iface.ns); | |
41 | } | |
2410aa96 | 42 | if (aad(sa)->name) { |
736ec752 | 43 | audit_log_format(ab, " name="); |
2410aa96 | 44 | audit_log_untrustedstring(ab, aad(sa)->name); |
736ec752 | 45 | } |
ef88a7ac JJ |
46 | if (aad(sa)->iface.pos) |
47 | audit_log_format(ab, " offset=%ld", aad(sa)->iface.pos); | |
736ec752 JJ |
48 | } |
49 | ||
50 | /** | |
51 | * audit_iface - do audit message for policy unpacking/load/replace/remove | |
52 | * @new: profile if it has been allocated (MAYBE NULL) | |
04dc715e | 53 | * @ns_name: name of the ns the profile is to be loaded to (MAY BE NULL) |
736ec752 JJ |
54 | * @name: name of the profile being manipulated (MAYBE NULL) |
55 | * @info: any extra info about the failure (MAYBE NULL) | |
b1b4bc2e | 56 | * @e: buffer position info |
736ec752 JJ |
57 | * @error: error code |
58 | * | |
59 | * Returns: %0 or error | |
60 | */ | |
04dc715e JJ |
61 | static int audit_iface(struct aa_profile *new, const char *ns_name, |
62 | const char *name, const char *info, struct aa_ext *e, | |
63 | int error) | |
736ec752 | 64 | { |
637f688d | 65 | struct aa_profile *profile = labels_profile(aa_current_raw_label()); |
8c4b785a | 66 | DEFINE_AUDIT_DATA(sa, LSM_AUDIT_DATA_NONE, AA_CLASS_NONE, NULL); |
b1b4bc2e | 67 | if (e) |
ef88a7ac JJ |
68 | aad(&sa)->iface.pos = e->pos - e->start; |
69 | aad(&sa)->iface.ns = ns_name; | |
70 | if (new) | |
2410aa96 | 71 | aad(&sa)->name = new->base.hname; |
ef88a7ac | 72 | else |
2410aa96 | 73 | aad(&sa)->name = name; |
ef88a7ac JJ |
74 | aad(&sa)->info = info; |
75 | aad(&sa)->error = error; | |
736ec752 | 76 | |
ef88a7ac | 77 | return aa_audit(AUDIT_APPARMOR_STATUS, profile, &sa, audit_cb); |
736ec752 JJ |
78 | } |
79 | ||
5d5182ca JJ |
80 | void __aa_loaddata_update(struct aa_loaddata *data, long revision) |
81 | { | |
82 | AA_BUG(!data); | |
83 | AA_BUG(!data->ns); | |
5d5182ca JJ |
84 | AA_BUG(!mutex_is_locked(&data->ns->lock)); |
85 | AA_BUG(data->revision > revision); | |
86 | ||
87 | data->revision = revision; | |
d61c57fd JJ |
88 | if ((data->dents[AAFS_LOADDATA_REVISION])) { |
89 | d_inode(data->dents[AAFS_LOADDATA_DIR])->i_mtime = | |
90 | current_time(d_inode(data->dents[AAFS_LOADDATA_DIR])); | |
91 | d_inode(data->dents[AAFS_LOADDATA_REVISION])->i_mtime = | |
92 | current_time(d_inode(data->dents[AAFS_LOADDATA_REVISION])); | |
93 | } | |
5d5182ca JJ |
94 | } |
95 | ||
96 | bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r) | |
97 | { | |
98 | if (l->size != r->size) | |
99 | return false; | |
63c16c3a CC |
100 | if (l->compressed_size != r->compressed_size) |
101 | return false; | |
5d5182ca JJ |
102 | if (aa_g_hash_policy && memcmp(l->hash, r->hash, aa_hash_size()) != 0) |
103 | return false; | |
63c16c3a | 104 | return memcmp(l->data, r->data, r->compressed_size ?: r->size) == 0; |
5d5182ca JJ |
105 | } |
106 | ||
107 | /* | |
108 | * need to take the ns mutex lock which is NOT safe most places that | |
109 | * put_loaddata is called, so we have to delay freeing it | |
110 | */ | |
111 | static void do_loaddata_free(struct work_struct *work) | |
112 | { | |
113 | struct aa_loaddata *d = container_of(work, struct aa_loaddata, work); | |
114 | struct aa_ns *ns = aa_get_ns(d->ns); | |
115 | ||
116 | if (ns) { | |
feb3c766 | 117 | mutex_lock_nested(&ns->lock, ns->level); |
5d5182ca JJ |
118 | __aa_fs_remove_rawdata(d); |
119 | mutex_unlock(&ns->lock); | |
120 | aa_put_ns(ns); | |
121 | } | |
122 | ||
453431a5 WL |
123 | kfree_sensitive(d->hash); |
124 | kfree_sensitive(d->name); | |
a6a52579 | 125 | kvfree(d->data); |
453431a5 | 126 | kfree_sensitive(d); |
5d5182ca JJ |
127 | } |
128 | ||
5ac8c355 JJ |
129 | void aa_loaddata_kref(struct kref *kref) |
130 | { | |
131 | struct aa_loaddata *d = container_of(kref, struct aa_loaddata, count); | |
132 | ||
133 | if (d) { | |
5d5182ca JJ |
134 | INIT_WORK(&d->work, do_loaddata_free); |
135 | schedule_work(&d->work); | |
5ac8c355 JJ |
136 | } |
137 | } | |
138 | ||
5d5182ca JJ |
139 | struct aa_loaddata *aa_loaddata_alloc(size_t size) |
140 | { | |
a6a52579 | 141 | struct aa_loaddata *d; |
5d5182ca | 142 | |
a6a52579 | 143 | d = kzalloc(sizeof(*d), GFP_KERNEL); |
5d5182ca JJ |
144 | if (d == NULL) |
145 | return ERR_PTR(-ENOMEM); | |
a6a52579 JJ |
146 | d->data = kvzalloc(size, GFP_KERNEL); |
147 | if (!d->data) { | |
148 | kfree(d); | |
149 | return ERR_PTR(-ENOMEM); | |
150 | } | |
5d5182ca JJ |
151 | kref_init(&d->count); |
152 | INIT_LIST_HEAD(&d->list); | |
153 | ||
154 | return d; | |
155 | } | |
156 | ||
736ec752 | 157 | /* test if read will be in packed data bounds */ |
b11e51dd | 158 | VISIBLE_IF_KUNIT bool aa_inbounds(struct aa_ext *e, size_t size) |
736ec752 JJ |
159 | { |
160 | return (size <= e->end - e->pos); | |
161 | } | |
b11e51dd | 162 | EXPORT_SYMBOL_IF_KUNIT(aa_inbounds); |
736ec752 JJ |
163 | |
164 | /** | |
b11e51dd | 165 | * aa_unpack_u16_chunk - test and do bounds checking for a u16 size based chunk |
736ec752 JJ |
166 | * @e: serialized data read head (NOT NULL) |
167 | * @chunk: start address for chunk of data (NOT NULL) | |
168 | * | |
169 | * Returns: the size of chunk found with the read head at the end of the chunk. | |
170 | */ | |
b11e51dd | 171 | VISIBLE_IF_KUNIT size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk) |
736ec752 JJ |
172 | { |
173 | size_t size = 0; | |
156e4299 | 174 | void *pos = e->pos; |
736ec752 | 175 | |
b11e51dd | 176 | if (!aa_inbounds(e, sizeof(u16))) |
156e4299 | 177 | goto fail; |
2c17cd36 JJ |
178 | size = le16_to_cpu(get_unaligned((__le16 *) e->pos)); |
179 | e->pos += sizeof(__le16); | |
b11e51dd | 180 | if (!aa_inbounds(e, size)) |
156e4299 | 181 | goto fail; |
736ec752 JJ |
182 | *chunk = e->pos; |
183 | e->pos += size; | |
184 | return size; | |
156e4299 MS |
185 | |
186 | fail: | |
187 | e->pos = pos; | |
188 | return 0; | |
736ec752 | 189 | } |
b11e51dd | 190 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u16_chunk); |
736ec752 JJ |
191 | |
192 | /* unpack control byte */ | |
b11e51dd | 193 | VISIBLE_IF_KUNIT bool aa_unpack_X(struct aa_ext *e, enum aa_code code) |
736ec752 | 194 | { |
b11e51dd | 195 | if (!aa_inbounds(e, 1)) |
e3798609 | 196 | return false; |
736ec752 | 197 | if (*(u8 *) e->pos != code) |
e3798609 | 198 | return false; |
736ec752 | 199 | e->pos++; |
e3798609 | 200 | return true; |
736ec752 | 201 | } |
b11e51dd | 202 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_X); |
736ec752 JJ |
203 | |
204 | /** | |
b11e51dd | 205 | * aa_unpack_nameX - check is the next element is of type X with a name of @name |
736ec752 JJ |
206 | * @e: serialized data extent information (NOT NULL) |
207 | * @code: type code | |
208 | * @name: name to match to the serialized element. (MAYBE NULL) | |
209 | * | |
210 | * check that the next serialized data element is of type X and has a tag | |
211 | * name @name. If @name is specified then there must be a matching | |
212 | * name element in the stream. If @name is NULL any name element will be | |
213 | * skipped and only the typecode will be tested. | |
214 | * | |
e3798609 | 215 | * Returns true on success (both type code and name tests match) and the read |
736ec752 JJ |
216 | * head is advanced past the headers |
217 | * | |
e3798609 | 218 | * Returns: false if either match fails, the read head does not move |
736ec752 | 219 | */ |
b11e51dd | 220 | VISIBLE_IF_KUNIT bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name) |
736ec752 JJ |
221 | { |
222 | /* | |
223 | * May need to reset pos if name or type doesn't match | |
224 | */ | |
225 | void *pos = e->pos; | |
226 | /* | |
227 | * Check for presence of a tagname, and if present name size | |
228 | * AA_NAME tag value is a u16. | |
229 | */ | |
b11e51dd | 230 | if (aa_unpack_X(e, AA_NAME)) { |
736ec752 | 231 | char *tag = NULL; |
b11e51dd | 232 | size_t size = aa_unpack_u16_chunk(e, &tag); |
736ec752 | 233 | /* if a name is specified it must match. otherwise skip tag */ |
8404d7a6 | 234 | if (name && (!size || tag[size-1] != '\0' || strcmp(name, tag))) |
736ec752 JJ |
235 | goto fail; |
236 | } else if (name) { | |
237 | /* if a name is specified and there is no name tag fail */ | |
238 | goto fail; | |
239 | } | |
240 | ||
241 | /* now check if type code matches */ | |
b11e51dd | 242 | if (aa_unpack_X(e, code)) |
e3798609 | 243 | return true; |
736ec752 JJ |
244 | |
245 | fail: | |
246 | e->pos = pos; | |
e3798609 | 247 | return false; |
736ec752 | 248 | } |
b11e51dd | 249 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_nameX); |
736ec752 | 250 | |
9caafbe2 MG |
251 | static bool unpack_u8(struct aa_ext *e, u8 *data, const char *name) |
252 | { | |
156e4299 MS |
253 | void *pos = e->pos; |
254 | ||
b11e51dd RM |
255 | if (aa_unpack_nameX(e, AA_U8, name)) { |
256 | if (!aa_inbounds(e, sizeof(u8))) | |
156e4299 | 257 | goto fail; |
9caafbe2 | 258 | if (data) |
dd979d7a | 259 | *data = *((u8 *)e->pos); |
9caafbe2 | 260 | e->pos += sizeof(u8); |
e3798609 | 261 | return true; |
9caafbe2 | 262 | } |
156e4299 MS |
263 | |
264 | fail: | |
265 | e->pos = pos; | |
e3798609 | 266 | return false; |
9caafbe2 MG |
267 | } |
268 | ||
b11e51dd | 269 | VISIBLE_IF_KUNIT bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name) |
736ec752 | 270 | { |
156e4299 MS |
271 | void *pos = e->pos; |
272 | ||
b11e51dd RM |
273 | if (aa_unpack_nameX(e, AA_U32, name)) { |
274 | if (!aa_inbounds(e, sizeof(u32))) | |
156e4299 | 275 | goto fail; |
736ec752 | 276 | if (data) |
2c17cd36 | 277 | *data = le32_to_cpu(get_unaligned((__le32 *) e->pos)); |
736ec752 | 278 | e->pos += sizeof(u32); |
e3798609 | 279 | return true; |
736ec752 | 280 | } |
156e4299 MS |
281 | |
282 | fail: | |
283 | e->pos = pos; | |
e3798609 | 284 | return false; |
736ec752 | 285 | } |
b11e51dd | 286 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u32); |
736ec752 | 287 | |
b11e51dd | 288 | VISIBLE_IF_KUNIT bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name) |
736ec752 | 289 | { |
156e4299 MS |
290 | void *pos = e->pos; |
291 | ||
b11e51dd RM |
292 | if (aa_unpack_nameX(e, AA_U64, name)) { |
293 | if (!aa_inbounds(e, sizeof(u64))) | |
156e4299 | 294 | goto fail; |
736ec752 | 295 | if (data) |
2c17cd36 | 296 | *data = le64_to_cpu(get_unaligned((__le64 *) e->pos)); |
736ec752 | 297 | e->pos += sizeof(u64); |
e3798609 | 298 | return true; |
736ec752 | 299 | } |
156e4299 MS |
300 | |
301 | fail: | |
302 | e->pos = pos; | |
e3798609 | 303 | return false; |
736ec752 | 304 | } |
b11e51dd | 305 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u64); |
736ec752 | 306 | |
f122a08b LT |
307 | static bool aa_unpack_cap_low(struct aa_ext *e, kernel_cap_t *data, const char *name) |
308 | { | |
309 | u32 val; | |
310 | ||
311 | if (!aa_unpack_u32(e, &val, name)) | |
312 | return false; | |
313 | data->val = val; | |
314 | return true; | |
315 | } | |
316 | ||
317 | static bool aa_unpack_cap_high(struct aa_ext *e, kernel_cap_t *data, const char *name) | |
318 | { | |
319 | u32 val; | |
320 | ||
321 | if (!aa_unpack_u32(e, &val, name)) | |
322 | return false; | |
323 | data->val = (u32)data->val | ((u64)val << 32); | |
324 | return true; | |
325 | } | |
326 | ||
93761c93 | 327 | VISIBLE_IF_KUNIT bool aa_unpack_array(struct aa_ext *e, const char *name, u16 *size) |
736ec752 | 328 | { |
156e4299 MS |
329 | void *pos = e->pos; |
330 | ||
b11e51dd | 331 | if (aa_unpack_nameX(e, AA_ARRAY, name)) { |
b11e51dd | 332 | if (!aa_inbounds(e, sizeof(u16))) |
156e4299 | 333 | goto fail; |
371e50a0 | 334 | *size = le16_to_cpu(get_unaligned((__le16 *) e->pos)); |
736ec752 | 335 | e->pos += sizeof(u16); |
93761c93 | 336 | return true; |
736ec752 | 337 | } |
156e4299 MS |
338 | |
339 | fail: | |
340 | e->pos = pos; | |
93761c93 | 341 | return false; |
736ec752 | 342 | } |
b11e51dd | 343 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_array); |
736ec752 | 344 | |
b11e51dd | 345 | VISIBLE_IF_KUNIT size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name) |
736ec752 | 346 | { |
156e4299 MS |
347 | void *pos = e->pos; |
348 | ||
b11e51dd | 349 | if (aa_unpack_nameX(e, AA_BLOB, name)) { |
736ec752 | 350 | u32 size; |
b11e51dd | 351 | if (!aa_inbounds(e, sizeof(u32))) |
156e4299 | 352 | goto fail; |
2c17cd36 | 353 | size = le32_to_cpu(get_unaligned((__le32 *) e->pos)); |
736ec752 | 354 | e->pos += sizeof(u32); |
b11e51dd | 355 | if (aa_inbounds(e, (size_t) size)) { |
736ec752 JJ |
356 | *blob = e->pos; |
357 | e->pos += size; | |
358 | return size; | |
359 | } | |
360 | } | |
156e4299 MS |
361 | |
362 | fail: | |
363 | e->pos = pos; | |
736ec752 JJ |
364 | return 0; |
365 | } | |
b11e51dd | 366 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_blob); |
736ec752 | 367 | |
b11e51dd | 368 | VISIBLE_IF_KUNIT int aa_unpack_str(struct aa_ext *e, const char **string, const char *name) |
736ec752 JJ |
369 | { |
370 | char *src_str; | |
371 | size_t size = 0; | |
372 | void *pos = e->pos; | |
373 | *string = NULL; | |
b11e51dd RM |
374 | if (aa_unpack_nameX(e, AA_STRING, name)) { |
375 | size = aa_unpack_u16_chunk(e, &src_str); | |
736ec752 JJ |
376 | if (size) { |
377 | /* strings are null terminated, length is size - 1 */ | |
378 | if (src_str[size - 1] != 0) | |
379 | goto fail; | |
380 | *string = src_str; | |
156e4299 MS |
381 | |
382 | return size; | |
736ec752 JJ |
383 | } |
384 | } | |
736ec752 JJ |
385 | |
386 | fail: | |
387 | e->pos = pos; | |
388 | return 0; | |
389 | } | |
b11e51dd | 390 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_str); |
736ec752 | 391 | |
b11e51dd | 392 | VISIBLE_IF_KUNIT int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name) |
736ec752 JJ |
393 | { |
394 | const char *tmp; | |
395 | void *pos = e->pos; | |
b11e51dd | 396 | int res = aa_unpack_str(e, &tmp, name); |
736ec752 JJ |
397 | *string = NULL; |
398 | ||
399 | if (!res) | |
400 | return 0; | |
401 | ||
402 | *string = kmemdup(tmp, res, GFP_KERNEL); | |
403 | if (!*string) { | |
404 | e->pos = pos; | |
405 | return 0; | |
406 | } | |
407 | ||
408 | return res; | |
409 | } | |
b11e51dd | 410 | EXPORT_SYMBOL_IF_KUNIT(aa_unpack_strdup); |
736ec752 | 411 | |
736ec752 JJ |
412 | |
413 | /** | |
414 | * unpack_dfa - unpack a file rule dfa | |
415 | * @e: serialized data extent information (NOT NULL) | |
fd1b2b95 | 416 | * @flags: dfa flags to check |
736ec752 JJ |
417 | * |
418 | * returns dfa or ERR_PTR or NULL if no dfa | |
419 | */ | |
fd1b2b95 | 420 | static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags) |
736ec752 JJ |
421 | { |
422 | char *blob = NULL; | |
423 | size_t size; | |
424 | struct aa_dfa *dfa = NULL; | |
425 | ||
b11e51dd | 426 | size = aa_unpack_blob(e, &blob, "aadfa"); |
736ec752 JJ |
427 | if (size) { |
428 | /* | |
429 | * The dfa is aligned with in the blob to 8 bytes | |
430 | * from the beginning of the stream. | |
dd51c848 | 431 | * alignment adjust needed by dfa unpack |
736ec752 | 432 | */ |
dd51c848 JJ |
433 | size_t sz = blob - (char *) e->start - |
434 | ((e->pos - e->start) & 7); | |
736ec752 | 435 | size_t pad = ALIGN(sz, 8) - sz; |
5bfcbd22 JJ |
436 | if (aa_g_paranoid_load) |
437 | flags |= DFA_FLAG_VERIFY_STATES; | |
736ec752 JJ |
438 | dfa = aa_dfa_unpack(blob + pad, size - pad, flags); |
439 | ||
440 | if (IS_ERR(dfa)) | |
441 | return dfa; | |
442 | ||
736ec752 JJ |
443 | } |
444 | ||
445 | return dfa; | |
736ec752 JJ |
446 | } |
447 | ||
448 | /** | |
449 | * unpack_trans_table - unpack a profile transition table | |
450 | * @e: serialized data extent information (NOT NULL) | |
a0792e2c | 451 | * @table: str table to unpack to (NOT NULL) |
736ec752 | 452 | * |
a0792e2c | 453 | * Returns: true if table successfully unpacked or not present |
736ec752 | 454 | */ |
a0792e2c | 455 | static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) |
736ec752 | 456 | { |
19fe43a5 | 457 | void *saved_pos = e->pos; |
ee21a175 | 458 | char **table = NULL; |
736ec752 JJ |
459 | |
460 | /* exec table is optional */ | |
b11e51dd | 461 | if (aa_unpack_nameX(e, AA_STRUCT, "xtable")) { |
371e50a0 JJ |
462 | u16 size; |
463 | int i; | |
736ec752 | 464 | |
93761c93 | 465 | if (!aa_unpack_array(e, NULL, &size)) |
73c7e91c JJ |
466 | /* |
467 | * Note: index into trans table array is a max | |
468 | * of 2^24, but unpack array can only unpack | |
469 | * an array of 2^16 in size atm so no need | |
470 | * for size check here | |
471 | */ | |
736ec752 | 472 | goto fail; |
a0792e2c JJ |
473 | table = kcalloc(size, sizeof(char *), GFP_KERNEL); |
474 | if (!table) | |
736ec752 JJ |
475 | goto fail; |
476 | ||
736ec752 JJ |
477 | for (i = 0; i < size; i++) { |
478 | char *str; | |
b11e51dd RM |
479 | int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL); |
480 | /* aa_unpack_strdup verifies that the last character is | |
736ec752 JJ |
481 | * null termination byte. |
482 | */ | |
7ee95850 | 483 | if (!size2) |
736ec752 | 484 | goto fail; |
a0792e2c | 485 | table[i] = str; |
736ec752 JJ |
486 | /* verify that name doesn't start with space */ |
487 | if (isspace(*str)) | |
488 | goto fail; | |
489 | ||
490 | /* count internal # of internal \0 */ | |
5379a331 JJ |
491 | for (c = j = 0; j < size2 - 1; j++) { |
492 | if (!str[j]) { | |
493 | pos = j; | |
736ec752 | 494 | c++; |
5379a331 | 495 | } |
736ec752 JJ |
496 | } |
497 | if (*str == ':') { | |
5379a331 JJ |
498 | /* first character after : must be valid */ |
499 | if (!str[1]) | |
500 | goto fail; | |
736ec752 JJ |
501 | /* beginning with : requires an embedded \0, |
502 | * verify that exactly 1 internal \0 exists | |
b11e51dd | 503 | * trailing \0 already verified by aa_unpack_strdup |
5379a331 JJ |
504 | * |
505 | * convert \0 back to : for label_parse | |
736ec752 | 506 | */ |
5379a331 JJ |
507 | if (c == 1) |
508 | str[pos] = ':'; | |
509 | else if (c > 1) | |
736ec752 JJ |
510 | goto fail; |
511 | } else if (c) | |
512 | /* fail - all other cases with embedded \0 */ | |
513 | goto fail; | |
514 | } | |
b11e51dd | 515 | if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) |
736ec752 | 516 | goto fail; |
b11e51dd | 517 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
736ec752 | 518 | goto fail; |
a0792e2c JJ |
519 | |
520 | strs->table = table; | |
521 | strs->size = size; | |
736ec752 | 522 | } |
e3798609 | 523 | return true; |
736ec752 JJ |
524 | |
525 | fail: | |
a0792e2c | 526 | kfree_sensitive(table); |
19fe43a5 | 527 | e->pos = saved_pos; |
e3798609 | 528 | return false; |
736ec752 JJ |
529 | } |
530 | ||
8e51f908 MG |
531 | static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile) |
532 | { | |
533 | void *pos = e->pos; | |
534 | ||
b11e51dd | 535 | if (aa_unpack_nameX(e, AA_STRUCT, "xattrs")) { |
371e50a0 JJ |
536 | u16 size; |
537 | int i; | |
8e51f908 | 538 | |
93761c93 | 539 | if (!aa_unpack_array(e, NULL, &size)) |
371e50a0 | 540 | goto fail; |
217af7e2 JJ |
541 | profile->attach.xattr_count = size; |
542 | profile->attach.xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL); | |
543 | if (!profile->attach.xattrs) | |
8e51f908 MG |
544 | goto fail; |
545 | for (i = 0; i < size; i++) { | |
93761c93 | 546 | if (!aa_unpack_strdup(e, &profile->attach.xattrs[i], NULL)) |
8e51f908 MG |
547 | goto fail; |
548 | } | |
b11e51dd | 549 | if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) |
8e51f908 | 550 | goto fail; |
b11e51dd | 551 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
8e51f908 MG |
552 | goto fail; |
553 | } | |
554 | ||
e3798609 | 555 | return true; |
8e51f908 MG |
556 | |
557 | fail: | |
558 | e->pos = pos; | |
e3798609 | 559 | return false; |
8e51f908 MG |
560 | } |
561 | ||
1ad22fcc | 562 | static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules) |
9caafbe2 MG |
563 | { |
564 | void *pos = e->pos; | |
371e50a0 JJ |
565 | u16 size; |
566 | int i; | |
9caafbe2 | 567 | |
b11e51dd | 568 | if (aa_unpack_nameX(e, AA_STRUCT, "secmark")) { |
93761c93 | 569 | if (!aa_unpack_array(e, NULL, &size)) |
371e50a0 | 570 | goto fail; |
9caafbe2 | 571 | |
217af7e2 | 572 | rules->secmark = kcalloc(size, sizeof(struct aa_secmark), |
9caafbe2 | 573 | GFP_KERNEL); |
217af7e2 | 574 | if (!rules->secmark) |
9caafbe2 MG |
575 | goto fail; |
576 | ||
217af7e2 | 577 | rules->secmark_count = size; |
9caafbe2 MG |
578 | |
579 | for (i = 0; i < size; i++) { | |
217af7e2 | 580 | if (!unpack_u8(e, &rules->secmark[i].audit, NULL)) |
9caafbe2 | 581 | goto fail; |
217af7e2 | 582 | if (!unpack_u8(e, &rules->secmark[i].deny, NULL)) |
9caafbe2 | 583 | goto fail; |
93761c93 | 584 | if (!aa_unpack_strdup(e, &rules->secmark[i].label, NULL)) |
9caafbe2 MG |
585 | goto fail; |
586 | } | |
b11e51dd | 587 | if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) |
9caafbe2 | 588 | goto fail; |
b11e51dd | 589 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
9caafbe2 MG |
590 | goto fail; |
591 | } | |
592 | ||
e3798609 | 593 | return true; |
9caafbe2 MG |
594 | |
595 | fail: | |
217af7e2 | 596 | if (rules->secmark) { |
9caafbe2 | 597 | for (i = 0; i < size; i++) |
217af7e2 JJ |
598 | kfree(rules->secmark[i].label); |
599 | kfree(rules->secmark); | |
600 | rules->secmark_count = 0; | |
601 | rules->secmark = NULL; | |
9caafbe2 MG |
602 | } |
603 | ||
604 | e->pos = pos; | |
e3798609 | 605 | return false; |
9caafbe2 MG |
606 | } |
607 | ||
1ad22fcc | 608 | static bool unpack_rlimits(struct aa_ext *e, struct aa_ruleset *rules) |
736ec752 JJ |
609 | { |
610 | void *pos = e->pos; | |
611 | ||
612 | /* rlimits are optional */ | |
b11e51dd | 613 | if (aa_unpack_nameX(e, AA_STRUCT, "rlimits")) { |
371e50a0 JJ |
614 | u16 size; |
615 | int i; | |
736ec752 | 616 | u32 tmp = 0; |
b11e51dd | 617 | if (!aa_unpack_u32(e, &tmp, NULL)) |
736ec752 | 618 | goto fail; |
1ad22fcc | 619 | rules->rlimits.mask = tmp; |
736ec752 | 620 | |
93761c93 | 621 | if (!aa_unpack_array(e, NULL, &size) || |
371e50a0 | 622 | size > RLIM_NLIMITS) |
736ec752 JJ |
623 | goto fail; |
624 | for (i = 0; i < size; i++) { | |
7ee95850 | 625 | u64 tmp2 = 0; |
736ec752 | 626 | int a = aa_map_resource(i); |
b11e51dd | 627 | if (!aa_unpack_u64(e, &tmp2, NULL)) |
736ec752 | 628 | goto fail; |
1ad22fcc | 629 | rules->rlimits.limits[a].rlim_max = tmp2; |
736ec752 | 630 | } |
b11e51dd | 631 | if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) |
736ec752 | 632 | goto fail; |
b11e51dd | 633 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
736ec752 JJ |
634 | goto fail; |
635 | } | |
e3798609 | 636 | return true; |
736ec752 JJ |
637 | |
638 | fail: | |
639 | e->pos = pos; | |
e3798609 | 640 | return false; |
736ec752 JJ |
641 | } |
642 | ||
fd1b2b95 JJ |
643 | static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm) |
644 | { | |
fd1b2b95 JJ |
645 | if (version != 1) |
646 | return false; | |
647 | ||
93761c93 LT |
648 | return aa_unpack_u32(e, &perm->allow, NULL) && |
649 | aa_unpack_u32(e, &perm->allow, NULL) && | |
650 | aa_unpack_u32(e, &perm->deny, NULL) && | |
651 | aa_unpack_u32(e, &perm->subtree, NULL) && | |
652 | aa_unpack_u32(e, &perm->cond, NULL) && | |
653 | aa_unpack_u32(e, &perm->kill, NULL) && | |
654 | aa_unpack_u32(e, &perm->complain, NULL) && | |
655 | aa_unpack_u32(e, &perm->prompt, NULL) && | |
656 | aa_unpack_u32(e, &perm->audit, NULL) && | |
657 | aa_unpack_u32(e, &perm->quiet, NULL) && | |
658 | aa_unpack_u32(e, &perm->hide, NULL) && | |
659 | aa_unpack_u32(e, &perm->xindex, NULL) && | |
660 | aa_unpack_u32(e, &perm->tag, NULL) && | |
661 | aa_unpack_u32(e, &perm->label, NULL); | |
fd1b2b95 JJ |
662 | } |
663 | ||
664 | static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms) | |
665 | { | |
666 | void *pos = e->pos; | |
667 | u16 size = 0; | |
668 | ||
669 | AA_BUG(!perms); | |
670 | /* | |
671 | * policy perms are optional, in which case perms are embedded | |
672 | * in the dfa accept table | |
673 | */ | |
93761c93 | 674 | if (aa_unpack_nameX(e, AA_STRUCT, "perms")) { |
fd1b2b95 JJ |
675 | int i; |
676 | u32 version; | |
677 | ||
93761c93 | 678 | if (!aa_unpack_u32(e, &version, "version")) |
fd1b2b95 | 679 | goto fail_reset; |
93761c93 | 680 | if (!aa_unpack_array(e, NULL, &size)) |
fd1b2b95 JJ |
681 | goto fail_reset; |
682 | *perms = kcalloc(size, sizeof(struct aa_perms), GFP_KERNEL); | |
683 | if (!*perms) | |
684 | goto fail_reset; | |
685 | for (i = 0; i < size; i++) { | |
686 | if (!unpack_perm(e, version, &(*perms)[i])) | |
687 | goto fail; | |
688 | } | |
93761c93 | 689 | if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) |
fd1b2b95 | 690 | goto fail; |
93761c93 | 691 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
fd1b2b95 JJ |
692 | goto fail; |
693 | } else | |
694 | *perms = NULL; | |
695 | ||
696 | return size; | |
697 | ||
698 | fail: | |
699 | kfree(*perms); | |
700 | fail_reset: | |
701 | e->pos = pos; | |
702 | return -EPROTO; | |
703 | } | |
704 | ||
ad596ea7 JJ |
705 | static int unpack_pdb(struct aa_ext *e, struct aa_policydb *policy, |
706 | bool required_dfa, bool required_trans, | |
707 | const char **info) | |
708 | { | |
fd1b2b95 JJ |
709 | void *pos = e->pos; |
710 | int i, flags, error = -EPROTO; | |
5515a8e3 | 711 | ssize_t size; |
ad596ea7 | 712 | |
5515a8e3 MUA |
713 | size = unpack_perms_table(e, &policy->perms); |
714 | if (size < 0) { | |
715 | error = size; | |
fd1b2b95 JJ |
716 | policy->perms = NULL; |
717 | *info = "failed to unpack - perms"; | |
718 | goto fail; | |
5515a8e3 MUA |
719 | } |
720 | policy->size = size; | |
721 | ||
722 | if (policy->perms) { | |
fd1b2b95 JJ |
723 | /* perms table present accept is index */ |
724 | flags = TO_ACCEPT1_FLAG(YYTD_DATA32); | |
725 | } else { | |
726 | /* packed perms in accept1 and accept2 */ | |
727 | flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | | |
728 | TO_ACCEPT2_FLAG(YYTD_DATA32); | |
729 | } | |
ad596ea7 | 730 | |
fd1b2b95 JJ |
731 | policy->dfa = unpack_dfa(e, flags); |
732 | if (IS_ERR(policy->dfa)) { | |
733 | error = PTR_ERR(policy->dfa); | |
ad596ea7 JJ |
734 | policy->dfa = NULL; |
735 | *info = "failed to unpack - dfa"; | |
fd1b2b95 | 736 | goto fail; |
ad596ea7 JJ |
737 | } else if (!policy->dfa) { |
738 | if (required_dfa) { | |
739 | *info = "missing required dfa"; | |
fd1b2b95 | 740 | goto fail; |
ad596ea7 JJ |
741 | } |
742 | goto out; | |
743 | } | |
744 | ||
745 | /* | |
746 | * only unpack the following if a dfa is present | |
747 | * | |
748 | * sadly start was given different names for file and policydb | |
749 | * but since it is optional we can try both | |
750 | */ | |
93761c93 | 751 | if (!aa_unpack_u32(e, &policy->start[0], "start")) |
ad596ea7 JJ |
752 | /* default start state */ |
753 | policy->start[0] = DFA_START; | |
93761c93 | 754 | if (!aa_unpack_u32(e, &policy->start[AA_CLASS_FILE], "dfa_start")) { |
ad596ea7 JJ |
755 | /* default start state for xmatch and file dfa */ |
756 | policy->start[AA_CLASS_FILE] = DFA_START; | |
757 | } /* setup class index */ | |
758 | for (i = AA_CLASS_FILE + 1; i <= AA_CLASS_LAST; i++) { | |
759 | policy->start[i] = aa_dfa_next(policy->dfa, policy->start[0], | |
760 | i); | |
761 | } | |
762 | if (!unpack_trans_table(e, &policy->trans) && required_trans) { | |
763 | *info = "failed to unpack profile transition table"; | |
fd1b2b95 | 764 | goto fail; |
ad596ea7 | 765 | } |
ad596ea7 | 766 | |
670f3177 JJ |
767 | /* TODO: move compat mapping here, requires dfa merging first */ |
768 | /* TODO: move verify here, it has to be done after compat mappings */ | |
ad596ea7 JJ |
769 | out: |
770 | return 0; | |
fd1b2b95 JJ |
771 | |
772 | fail: | |
773 | e->pos = pos; | |
774 | return error; | |
ad596ea7 JJ |
775 | } |
776 | ||
e025be0f WH |
777 | static u32 strhash(const void *data, u32 len, u32 seed) |
778 | { | |
779 | const char * const *key = data; | |
780 | ||
781 | return jhash(*key, strlen(*key), seed); | |
782 | } | |
783 | ||
784 | static int datacmp(struct rhashtable_compare_arg *arg, const void *obj) | |
785 | { | |
786 | const struct aa_data *data = obj; | |
787 | const char * const *key = arg->key; | |
788 | ||
789 | return strcmp(data->key, *key); | |
790 | } | |
791 | ||
736ec752 JJ |
792 | /** |
793 | * unpack_profile - unpack a serialized profile | |
794 | * @e: serialized data extent information (NOT NULL) | |
5ee5d374 | 795 | * @ns_name: pointer of newly allocated copy of %NULL in case of error |
736ec752 JJ |
796 | * |
797 | * NOTE: unpack profile sets audit struct if there is a failure | |
798 | */ | |
04dc715e | 799 | static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) |
736ec752 | 800 | { |
217af7e2 | 801 | struct aa_ruleset *rules; |
736ec752 | 802 | struct aa_profile *profile = NULL; |
04dc715e | 803 | const char *tmpname, *tmpns = NULL, *name = NULL; |
2410aa96 | 804 | const char *info = "failed to unpack profile"; |
80c094a4 | 805 | size_t ns_len; |
e025be0f WH |
806 | struct rhashtable_params params = { 0 }; |
807 | char *key = NULL; | |
808 | struct aa_data *data; | |
ad596ea7 | 809 | int error = -EPROTO; |
736ec752 JJ |
810 | kernel_cap_t tmpcap; |
811 | u32 tmp; | |
812 | ||
04dc715e JJ |
813 | *ns_name = NULL; |
814 | ||
736ec752 | 815 | /* check that we have the right struct being passed */ |
b11e51dd | 816 | if (!aa_unpack_nameX(e, AA_STRUCT, "profile")) |
736ec752 | 817 | goto fail; |
b11e51dd | 818 | if (!aa_unpack_str(e, &name, NULL)) |
736ec752 | 819 | goto fail; |
04dc715e JJ |
820 | if (*name == '\0') |
821 | goto fail; | |
822 | ||
823 | tmpname = aa_splitn_fqname(name, strlen(name), &tmpns, &ns_len); | |
824 | if (tmpns) { | |
825 | *ns_name = kstrndup(tmpns, ns_len, GFP_KERNEL); | |
2410aa96 JJ |
826 | if (!*ns_name) { |
827 | info = "out of memory"; | |
53991aed | 828 | error = -ENOMEM; |
04dc715e | 829 | goto fail; |
2410aa96 | 830 | } |
04dc715e JJ |
831 | name = tmpname; |
832 | } | |
736ec752 | 833 | |
637f688d | 834 | profile = aa_alloc_profile(name, NULL, GFP_KERNEL); |
3265949f XJ |
835 | if (!profile) { |
836 | info = "out of memory"; | |
837 | error = -ENOMEM; | |
838 | goto fail; | |
839 | } | |
1ad22fcc | 840 | rules = list_first_entry(&profile->rules, typeof(*rules), list); |
736ec752 JJ |
841 | |
842 | /* profile renaming is optional */ | |
b11e51dd | 843 | (void) aa_unpack_str(e, &profile->rename, "rename"); |
736ec752 | 844 | |
556d0be7 | 845 | /* attachment string is optional */ |
93761c93 | 846 | (void) aa_unpack_str(e, &profile->attach.xmatch_str, "attach"); |
556d0be7 | 847 | |
736ec752 | 848 | /* xmatch is optional and may be NULL */ |
217af7e2 JJ |
849 | error = unpack_pdb(e, &profile->attach.xmatch, false, false, &info); |
850 | if (error) { | |
2410aa96 | 851 | info = "bad xmatch"; |
736ec752 JJ |
852 | goto fail; |
853 | } | |
ad596ea7 | 854 | |
b5b57993 | 855 | /* neither xmatch_len not xmatch_perms are optional if xmatch is set */ |
217af7e2 | 856 | if (profile->attach.xmatch.dfa) { |
b11e51dd | 857 | if (!aa_unpack_u32(e, &tmp, NULL)) { |
2410aa96 | 858 | info = "missing xmatch len"; |
736ec752 | 859 | goto fail; |
2410aa96 | 860 | } |
217af7e2 JJ |
861 | profile->attach.xmatch_len = tmp; |
862 | profile->attach.xmatch.start[AA_CLASS_XMATCH] = DFA_START; | |
53991aed JJ |
863 | error = aa_compat_map_xmatch(&profile->attach.xmatch); |
864 | if (error) { | |
e48ffd24 JJ |
865 | info = "failed to convert xmatch permission table"; |
866 | goto fail; | |
867 | } | |
736ec752 JJ |
868 | } |
869 | ||
72c8a768 | 870 | /* disconnected attachment string is optional */ |
b11e51dd | 871 | (void) aa_unpack_str(e, &profile->disconnected, "disconnected"); |
72c8a768 | 872 | |
736ec752 | 873 | /* per profile debug flags (complain, audit) */ |
b11e51dd | 874 | if (!aa_unpack_nameX(e, AA_STRUCT, "flags")) { |
2410aa96 | 875 | info = "profile missing flags"; |
736ec752 | 876 | goto fail; |
2410aa96 JJ |
877 | } |
878 | info = "failed to unpack profile flags"; | |
b11e51dd | 879 | if (!aa_unpack_u32(e, &tmp, NULL)) |
736ec752 | 880 | goto fail; |
03816507 | 881 | if (tmp & PACKED_FLAG_HAT) |
637f688d | 882 | profile->label.flags |= FLAG_HAT; |
c1ed5da1 JJ |
883 | if (tmp & PACKED_FLAG_DEBUG1) |
884 | profile->label.flags |= FLAG_DEBUG1; | |
885 | if (tmp & PACKED_FLAG_DEBUG2) | |
886 | profile->label.flags |= FLAG_DEBUG2; | |
b11e51dd | 887 | if (!aa_unpack_u32(e, &tmp, NULL)) |
736ec752 | 888 | goto fail; |
3bbb7b2e | 889 | if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) { |
736ec752 | 890 | profile->mode = APPARMOR_COMPLAIN; |
3bbb7b2e | 891 | } else if (tmp == PACKED_MODE_ENFORCE) { |
f05841a9 | 892 | profile->mode = APPARMOR_ENFORCE; |
3bbb7b2e | 893 | } else if (tmp == PACKED_MODE_KILL) { |
03816507 | 894 | profile->mode = APPARMOR_KILL; |
3bbb7b2e | 895 | } else if (tmp == PACKED_MODE_UNCONFINED) { |
03816507 | 896 | profile->mode = APPARMOR_UNCONFINED; |
3bbb7b2e | 897 | profile->label.flags |= FLAG_UNCONFINED; |
22fac8a0 JJ |
898 | } else if (tmp == PACKED_MODE_USER) { |
899 | profile->mode = APPARMOR_USER; | |
3bbb7b2e | 900 | } else { |
f05841a9 | 901 | goto fail; |
3bbb7b2e | 902 | } |
b11e51dd | 903 | if (!aa_unpack_u32(e, &tmp, NULL)) |
736ec752 JJ |
904 | goto fail; |
905 | if (tmp) | |
906 | profile->audit = AUDIT_ALL; | |
907 | ||
b11e51dd | 908 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
736ec752 JJ |
909 | goto fail; |
910 | ||
911 | /* path_flags is optional */ | |
b11e51dd | 912 | if (aa_unpack_u32(e, &profile->path_flags, "path_flags")) |
637f688d JJ |
913 | profile->path_flags |= profile->label.flags & |
914 | PATH_MEDIATE_DELETED; | |
736ec752 JJ |
915 | else |
916 | /* set a default value if path_flags field is not present */ | |
637f688d | 917 | profile->path_flags = PATH_MEDIATE_DELETED; |
736ec752 | 918 | |
2410aa96 | 919 | info = "failed to unpack profile capabilities"; |
f122a08b | 920 | if (!aa_unpack_cap_low(e, &rules->caps.allow, NULL)) |
736ec752 | 921 | goto fail; |
f122a08b | 922 | if (!aa_unpack_cap_low(e, &rules->caps.audit, NULL)) |
736ec752 | 923 | goto fail; |
f122a08b | 924 | if (!aa_unpack_cap_low(e, &rules->caps.quiet, NULL)) |
736ec752 | 925 | goto fail; |
f122a08b | 926 | if (!aa_unpack_cap_low(e, &tmpcap, NULL)) |
736ec752 JJ |
927 | goto fail; |
928 | ||
2410aa96 | 929 | info = "failed to unpack upper profile capabilities"; |
b11e51dd | 930 | if (aa_unpack_nameX(e, AA_STRUCT, "caps64")) { |
736ec752 | 931 | /* optional upper half of 64 bit caps */ |
f122a08b | 932 | if (!aa_unpack_cap_high(e, &rules->caps.allow, NULL)) |
736ec752 | 933 | goto fail; |
f122a08b | 934 | if (!aa_unpack_cap_high(e, &rules->caps.audit, NULL)) |
736ec752 | 935 | goto fail; |
f122a08b | 936 | if (!aa_unpack_cap_high(e, &rules->caps.quiet, NULL)) |
736ec752 | 937 | goto fail; |
f122a08b | 938 | if (!aa_unpack_cap_high(e, &tmpcap, NULL)) |
736ec752 | 939 | goto fail; |
b11e51dd | 940 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
736ec752 JJ |
941 | goto fail; |
942 | } | |
943 | ||
2410aa96 | 944 | info = "failed to unpack extended profile capabilities"; |
b11e51dd | 945 | if (aa_unpack_nameX(e, AA_STRUCT, "capsx")) { |
736ec752 | 946 | /* optional extended caps mediation mask */ |
f122a08b | 947 | if (!aa_unpack_cap_low(e, &rules->caps.extended, NULL)) |
736ec752 | 948 | goto fail; |
f122a08b | 949 | if (!aa_unpack_cap_high(e, &rules->caps.extended, NULL)) |
736ec752 | 950 | goto fail; |
b11e51dd | 951 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
cdbd2884 | 952 | goto fail; |
736ec752 JJ |
953 | } |
954 | ||
8e51f908 MG |
955 | if (!unpack_xattrs(e, profile)) { |
956 | info = "failed to unpack profile xattrs"; | |
957 | goto fail; | |
958 | } | |
959 | ||
1ad22fcc | 960 | if (!unpack_rlimits(e, rules)) { |
2410aa96 | 961 | info = "failed to unpack profile rlimits"; |
736ec752 | 962 | goto fail; |
2410aa96 | 963 | } |
736ec752 | 964 | |
1ad22fcc | 965 | if (!unpack_secmark(e, rules)) { |
9caafbe2 MG |
966 | info = "failed to unpack profile secmark rules"; |
967 | goto fail; | |
968 | } | |
969 | ||
b11e51dd | 970 | if (aa_unpack_nameX(e, AA_STRUCT, "policydb")) { |
ad5ff3db | 971 | /* generic policy dfa - optional and may be NULL */ |
2410aa96 | 972 | info = "failed to unpack policydb"; |
217af7e2 JJ |
973 | error = unpack_pdb(e, &rules->policy, true, false, |
974 | &info); | |
ad596ea7 | 975 | if (error) |
5f20fdfe | 976 | goto fail; |
ad596ea7 | 977 | /* Fixup: drop when we get rid of start array */ |
217af7e2 | 978 | if (aa_dfa_next(rules->policy.dfa, rules->policy.start[0], |
ad596ea7 | 979 | AA_CLASS_FILE)) |
217af7e2 JJ |
980 | rules->policy.start[AA_CLASS_FILE] = |
981 | aa_dfa_next(rules->policy.dfa, | |
982 | rules->policy.start[0], | |
ad596ea7 | 983 | AA_CLASS_FILE); |
b11e51dd | 984 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) |
ad5ff3db | 985 | goto fail; |
53991aed JJ |
986 | error = aa_compat_map_policy(&rules->policy, e->version); |
987 | if (error) { | |
e844fe9b JJ |
988 | info = "failed to remap policydb permission table"; |
989 | goto fail; | |
990 | } | |
11c236b8 | 991 | } else |
217af7e2 | 992 | rules->policy.dfa = aa_get_dfa(nulldfa); |
ad5ff3db | 993 | |
736ec752 | 994 | /* get file rules */ |
217af7e2 | 995 | error = unpack_pdb(e, &rules->file, false, true, &info); |
ad596ea7 | 996 | if (error) { |
736ec752 | 997 | goto fail; |
217af7e2 | 998 | } else if (rules->file.dfa) { |
53991aed JJ |
999 | error = aa_compat_map_file(&rules->file); |
1000 | if (error) { | |
7572fea3 JJ |
1001 | info = "failed to remap file permission table"; |
1002 | goto fail; | |
1003 | } | |
217af7e2 JJ |
1004 | } else if (rules->policy.dfa && |
1005 | rules->policy.start[AA_CLASS_FILE]) { | |
1006 | rules->file.dfa = aa_get_dfa(rules->policy.dfa); | |
1007 | rules->file.start[AA_CLASS_FILE] = rules->policy.start[AA_CLASS_FILE]; | |
11c236b8 | 1008 | } else |
217af7e2 | 1009 | rules->file.dfa = aa_get_dfa(nulldfa); |
736ec752 | 1010 | |
53991aed | 1011 | error = -EPROTO; |
b11e51dd | 1012 | if (aa_unpack_nameX(e, AA_STRUCT, "data")) { |
2410aa96 | 1013 | info = "out of memory"; |
e025be0f | 1014 | profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL); |
53991aed JJ |
1015 | if (!profile->data) { |
1016 | error = -ENOMEM; | |
e025be0f | 1017 | goto fail; |
53991aed | 1018 | } |
e025be0f WH |
1019 | params.nelem_hint = 3; |
1020 | params.key_len = sizeof(void *); | |
1021 | params.key_offset = offsetof(struct aa_data, key); | |
1022 | params.head_offset = offsetof(struct aa_data, head); | |
1023 | params.hashfn = strhash; | |
1024 | params.obj_cmpfn = datacmp; | |
1025 | ||
2410aa96 JJ |
1026 | if (rhashtable_init(profile->data, ¶ms)) { |
1027 | info = "failed to init key, value hash table"; | |
e025be0f | 1028 | goto fail; |
2410aa96 | 1029 | } |
e025be0f | 1030 | |
b11e51dd | 1031 | while (aa_unpack_strdup(e, &key, NULL)) { |
e025be0f WH |
1032 | data = kzalloc(sizeof(*data), GFP_KERNEL); |
1033 | if (!data) { | |
453431a5 | 1034 | kfree_sensitive(key); |
53991aed | 1035 | error = -ENOMEM; |
e025be0f WH |
1036 | goto fail; |
1037 | } | |
1038 | ||
1039 | data->key = key; | |
b11e51dd | 1040 | data->size = aa_unpack_blob(e, &data->data, NULL); |
0b7b8704 | 1041 | data->data = kvmemdup(data->data, data->size, GFP_KERNEL); |
e025be0f | 1042 | if (data->size && !data->data) { |
453431a5 WL |
1043 | kfree_sensitive(data->key); |
1044 | kfree_sensitive(data); | |
53991aed | 1045 | error = -ENOMEM; |
e025be0f WH |
1046 | goto fail; |
1047 | } | |
1048 | ||
1049 | rhashtable_insert_fast(profile->data, &data->head, | |
1050 | profile->data->p); | |
1051 | } | |
1052 | ||
b11e51dd | 1053 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) { |
2410aa96 | 1054 | info = "failed to unpack end of key, value data table"; |
e025be0f | 1055 | goto fail; |
2410aa96 | 1056 | } |
e025be0f WH |
1057 | } |
1058 | ||
b11e51dd | 1059 | if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) { |
2410aa96 | 1060 | info = "failed to unpack end of profile"; |
736ec752 | 1061 | goto fail; |
2410aa96 | 1062 | } |
736ec752 JJ |
1063 | |
1064 | return profile; | |
1065 | ||
1066 | fail: | |
53991aed JJ |
1067 | if (error == 0) |
1068 | /* default error covers most cases */ | |
1069 | error = -EPROTO; | |
3265949f XJ |
1070 | if (*ns_name) { |
1071 | kfree(*ns_name); | |
1072 | *ns_name = NULL; | |
1073 | } | |
736ec752 JJ |
1074 | if (profile) |
1075 | name = NULL; | |
1076 | else if (!name) | |
1077 | name = "unknown"; | |
2410aa96 | 1078 | audit_iface(profile, NULL, name, info, e, error); |
8651e1d6 | 1079 | aa_free_profile(profile); |
736ec752 JJ |
1080 | |
1081 | return ERR_PTR(error); | |
1082 | } | |
1083 | ||
1084 | /** | |
5ee5d374 | 1085 | * verify_header - unpack serialized stream header |
736ec752 | 1086 | * @e: serialized data read head (NOT NULL) |
dd51c848 | 1087 | * @required: whether the header is required or optional |
736ec752 JJ |
1088 | * @ns: Returns - namespace if one is specified else NULL (NOT NULL) |
1089 | * | |
1090 | * Returns: error or 0 if header is good | |
1091 | */ | |
dd51c848 | 1092 | static int verify_header(struct aa_ext *e, int required, const char **ns) |
736ec752 JJ |
1093 | { |
1094 | int error = -EPROTONOSUPPORT; | |
dd51c848 JJ |
1095 | const char *name = NULL; |
1096 | *ns = NULL; | |
1097 | ||
736ec752 | 1098 | /* get the interface version */ |
b11e51dd | 1099 | if (!aa_unpack_u32(e, &e->version, "version")) { |
dd51c848 | 1100 | if (required) { |
04dc715e | 1101 | audit_iface(NULL, NULL, NULL, "invalid profile format", |
dd51c848 JJ |
1102 | e, error); |
1103 | return error; | |
1104 | } | |
736ec752 JJ |
1105 | } |
1106 | ||
474d6b75 JJ |
1107 | /* Check that the interface version is currently supported. |
1108 | * if not specified use previous version | |
1109 | * Mask off everything that is not kernel abi version | |
1110 | */ | |
3c076531 | 1111 | if (VERSION_LT(e->version, v5) || VERSION_GT(e->version, v9)) { |
04dc715e | 1112 | audit_iface(NULL, NULL, NULL, "unsupported interface version", |
474d6b75 JJ |
1113 | e, error); |
1114 | return error; | |
1115 | } | |
dd51c848 | 1116 | |
736ec752 | 1117 | /* read the namespace if present */ |
b11e51dd | 1118 | if (aa_unpack_str(e, &name, "namespace")) { |
04dc715e JJ |
1119 | if (*name == '\0') { |
1120 | audit_iface(NULL, NULL, NULL, "invalid namespace name", | |
1121 | e, error); | |
1122 | return error; | |
1123 | } | |
145a0ef2 | 1124 | if (*ns && strcmp(*ns, name)) { |
04dc715e JJ |
1125 | audit_iface(NULL, NULL, NULL, "invalid ns change", e, |
1126 | error); | |
145a0ef2 JJ |
1127 | } else if (!*ns) { |
1128 | *ns = kstrdup(name, GFP_KERNEL); | |
1129 | if (!*ns) | |
1130 | return -ENOMEM; | |
1131 | } | |
dd51c848 | 1132 | } |
736ec752 JJ |
1133 | |
1134 | return 0; | |
1135 | } | |
1136 | ||
1137 | static bool verify_xindex(int xindex, int table_size) | |
1138 | { | |
1139 | int index, xtype; | |
1140 | xtype = xindex & AA_X_TYPE_MASK; | |
1141 | index = xindex & AA_X_INDEX_MASK; | |
23ca7b64 | 1142 | if (xtype == AA_X_TABLE && index >= table_size) |
e3798609 ZW |
1143 | return false; |
1144 | return true; | |
736ec752 JJ |
1145 | } |
1146 | ||
1147 | /* verify dfa xindexes are in range of transition tables */ | |
1148 | static bool verify_dfa_xindex(struct aa_dfa *dfa, int table_size) | |
1149 | { | |
1150 | int i; | |
1151 | for (i = 0; i < dfa->tables[YYTD_ID_ACCEPT]->td_lolen; i++) { | |
7572fea3 | 1152 | if (!verify_xindex(ACCEPT_TABLE(dfa)[i], table_size)) |
e3798609 | 1153 | return false; |
736ec752 | 1154 | } |
e3798609 | 1155 | return true; |
736ec752 JJ |
1156 | } |
1157 | ||
3bf3d728 JJ |
1158 | static bool verify_perm(struct aa_perms *perm) |
1159 | { | |
1160 | /* TODO: allow option to just force the perms into a valid state */ | |
1161 | if (perm->allow & perm->deny) | |
1162 | return false; | |
1163 | if (perm->subtree & ~perm->allow) | |
1164 | return false; | |
1165 | if (perm->cond & (perm->allow | perm->deny)) | |
1166 | return false; | |
1167 | if (perm->kill & perm->allow) | |
1168 | return false; | |
1169 | if (perm->complain & (perm->allow | perm->deny)) | |
1170 | return false; | |
1171 | if (perm->prompt & (perm->allow | perm->deny)) | |
1172 | return false; | |
1173 | if (perm->complain & perm->prompt) | |
1174 | return false; | |
1175 | if (perm->hide & perm->allow) | |
1176 | return false; | |
1177 | ||
1178 | return true; | |
1179 | } | |
1180 | ||
1181 | static bool verify_perms(struct aa_policydb *pdb) | |
670f3177 JJ |
1182 | { |
1183 | int i; | |
1184 | ||
1185 | for (i = 0; i < pdb->size; i++) { | |
3bf3d728 JJ |
1186 | if (!verify_perm(&pdb->perms[i])) |
1187 | return false; | |
1188 | /* verify indexes into str table */ | |
670f3177 | 1189 | if (pdb->perms[i].xindex >= pdb->trans.size) |
e3798609 | 1190 | return false; |
670f3177 JJ |
1191 | if (pdb->perms[i].tag >= pdb->trans.size) |
1192 | return false; | |
1193 | if (pdb->perms[i].label >= pdb->trans.size) | |
e3798609 | 1194 | return false; |
736ec752 | 1195 | } |
670f3177 | 1196 | |
e3798609 | 1197 | return true; |
736ec752 JJ |
1198 | } |
1199 | ||
1200 | /** | |
1201 | * verify_profile - Do post unpack analysis to verify profile consistency | |
1202 | * @profile: profile to verify (NOT NULL) | |
1203 | * | |
1204 | * Returns: 0 if passes verification else error | |
7572fea3 JJ |
1205 | * |
1206 | * This verification is post any unpack mapping or changes | |
736ec752 JJ |
1207 | */ |
1208 | static int verify_profile(struct aa_profile *profile) | |
1209 | { | |
1ad22fcc JJ |
1210 | struct aa_ruleset *rules = list_first_entry(&profile->rules, |
1211 | typeof(*rules), list); | |
1212 | if (!rules) | |
1213 | return 0; | |
1214 | ||
1215 | if ((rules->file.dfa && !verify_dfa_xindex(rules->file.dfa, | |
1216 | rules->file.trans.size)) || | |
1217 | (rules->policy.dfa && | |
1218 | !verify_dfa_xindex(rules->policy.dfa, rules->policy.trans.size))) { | |
7572fea3 JJ |
1219 | audit_iface(profile, NULL, NULL, |
1220 | "Unpack: Invalid named transition", NULL, -EPROTO); | |
abbf8734 | 1221 | return -EPROTO; |
736ec752 JJ |
1222 | } |
1223 | ||
1ad22fcc | 1224 | if (!verify_perms(&rules->file)) { |
670f3177 JJ |
1225 | audit_iface(profile, NULL, NULL, |
1226 | "Unpack: Invalid perm index", NULL, -EPROTO); | |
1227 | return -EPROTO; | |
1228 | } | |
1ad22fcc | 1229 | if (!verify_perms(&rules->policy)) { |
670f3177 JJ |
1230 | audit_iface(profile, NULL, NULL, |
1231 | "Unpack: Invalid perm index", NULL, -EPROTO); | |
1232 | return -EPROTO; | |
1233 | } | |
217af7e2 | 1234 | if (!verify_perms(&profile->attach.xmatch)) { |
670f3177 JJ |
1235 | audit_iface(profile, NULL, NULL, |
1236 | "Unpack: Invalid perm index", NULL, -EPROTO); | |
abbf8734 | 1237 | return -EPROTO; |
736ec752 JJ |
1238 | } |
1239 | ||
1240 | return 0; | |
1241 | } | |
1242 | ||
dd51c848 JJ |
1243 | void aa_load_ent_free(struct aa_load_ent *ent) |
1244 | { | |
1245 | if (ent) { | |
1246 | aa_put_profile(ent->rename); | |
1247 | aa_put_profile(ent->old); | |
1248 | aa_put_profile(ent->new); | |
04dc715e | 1249 | kfree(ent->ns_name); |
453431a5 | 1250 | kfree_sensitive(ent); |
dd51c848 JJ |
1251 | } |
1252 | } | |
1253 | ||
1254 | struct aa_load_ent *aa_load_ent_alloc(void) | |
1255 | { | |
1256 | struct aa_load_ent *ent = kzalloc(sizeof(*ent), GFP_KERNEL); | |
1257 | if (ent) | |
1258 | INIT_LIST_HEAD(&ent->list); | |
1259 | return ent; | |
1260 | } | |
1261 | ||
f4d6b94b | 1262 | static int compress_zstd(const char *src, size_t slen, char **dst, size_t *dlen) |
63c16c3a | 1263 | { |
f9da5b14 | 1264 | #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY |
f4d6b94b JT |
1265 | const zstd_parameters params = |
1266 | zstd_get_params(aa_g_rawdata_compression_level, slen); | |
1267 | const size_t wksp_len = zstd_cctx_workspace_bound(¶ms.cParams); | |
1268 | void *wksp = NULL; | |
1269 | zstd_cctx *ctx = NULL; | |
1270 | size_t out_len = zstd_compress_bound(slen); | |
1271 | void *out = NULL; | |
1272 | int ret = 0; | |
1273 | ||
1274 | out = kvzalloc(out_len, GFP_KERNEL); | |
1275 | if (!out) { | |
1276 | ret = -ENOMEM; | |
1277 | goto cleanup; | |
63c16c3a CC |
1278 | } |
1279 | ||
f4d6b94b JT |
1280 | wksp = kvzalloc(wksp_len, GFP_KERNEL); |
1281 | if (!wksp) { | |
1282 | ret = -ENOMEM; | |
1283 | goto cleanup; | |
63c16c3a CC |
1284 | } |
1285 | ||
f4d6b94b JT |
1286 | ctx = zstd_init_cctx(wksp, wksp_len); |
1287 | if (!ctx) { | |
1288 | ret = -EINVAL; | |
1289 | goto cleanup; | |
1290 | } | |
63c16c3a | 1291 | |
f4d6b94b | 1292 | out_len = zstd_compress_cctx(ctx, out, out_len, src, slen, ¶ms); |
a2f31df0 | 1293 | if (zstd_is_error(out_len) || out_len >= slen) { |
f4d6b94b JT |
1294 | ret = -EINVAL; |
1295 | goto cleanup; | |
63c16c3a | 1296 | } |
63c16c3a | 1297 | |
f4d6b94b JT |
1298 | if (is_vmalloc_addr(out)) { |
1299 | *dst = kvzalloc(out_len, GFP_KERNEL); | |
1300 | if (*dst) { | |
1301 | memcpy(*dst, out, out_len); | |
1302 | kvfree(out); | |
1303 | out = NULL; | |
63c16c3a | 1304 | } |
f4d6b94b | 1305 | } else { |
63c16c3a CC |
1306 | /* |
1307 | * If the staging buffer was kmalloc'd, then using krealloc is | |
1308 | * probably going to be faster. The destination buffer will | |
1309 | * always be smaller, so it's just shrunk, avoiding a memcpy | |
1310 | */ | |
f4d6b94b JT |
1311 | *dst = krealloc(out, out_len, GFP_KERNEL); |
1312 | } | |
63c16c3a | 1313 | |
f4d6b94b JT |
1314 | if (!*dst) { |
1315 | ret = -ENOMEM; | |
1316 | goto cleanup; | |
63c16c3a CC |
1317 | } |
1318 | ||
f4d6b94b | 1319 | *dlen = out_len; |
63c16c3a | 1320 | |
f4d6b94b JT |
1321 | cleanup: |
1322 | if (ret) { | |
1323 | kvfree(out); | |
1324 | *dst = NULL; | |
1325 | } | |
63c16c3a | 1326 | |
f4d6b94b JT |
1327 | kvfree(wksp); |
1328 | return ret; | |
f9da5b14 JJ |
1329 | #else |
1330 | *dlen = slen; | |
1331 | return 0; | |
1332 | #endif | |
63c16c3a CC |
1333 | } |
1334 | ||
1335 | static int compress_loaddata(struct aa_loaddata *data) | |
1336 | { | |
63c16c3a CC |
1337 | AA_BUG(data->compressed_size > 0); |
1338 | ||
1339 | /* | |
1340 | * Shortcut the no compression case, else we increase the amount of | |
1341 | * storage required by a small amount | |
1342 | */ | |
1343 | if (aa_g_rawdata_compression_level != 0) { | |
1344 | void *udata = data->data; | |
f4d6b94b JT |
1345 | int error = compress_zstd(udata, data->size, &data->data, |
1346 | &data->compressed_size); | |
a2f31df0 JJ |
1347 | if (error) { |
1348 | data->compressed_size = data->size; | |
63c16c3a | 1349 | return error; |
a2f31df0 | 1350 | } |
f9da5b14 JJ |
1351 | if (udata != data->data) |
1352 | kvfree(udata); | |
63c16c3a CC |
1353 | } else |
1354 | data->compressed_size = data->size; | |
1355 | ||
1356 | return 0; | |
1357 | } | |
1358 | ||
736ec752 | 1359 | /** |
dd51c848 | 1360 | * aa_unpack - unpack packed binary profile(s) data loaded from user space |
736ec752 | 1361 | * @udata: user data copied to kmem (NOT NULL) |
dd51c848 | 1362 | * @lh: list to place unpacked profiles in a aa_repl_ws |
736ec752 JJ |
1363 | * @ns: Returns namespace profile is in if specified else NULL (NOT NULL) |
1364 | * | |
dd51c848 JJ |
1365 | * Unpack user data and return refcounted allocated profile(s) stored in |
1366 | * @lh in order of discovery, with the list chain stored in base.list | |
1367 | * or error | |
736ec752 | 1368 | * |
dd51c848 | 1369 | * Returns: profile(s) on @lh else error pointer if fails to unpack |
736ec752 | 1370 | */ |
5ac8c355 JJ |
1371 | int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, |
1372 | const char **ns) | |
736ec752 | 1373 | { |
dd51c848 | 1374 | struct aa_load_ent *tmp, *ent; |
736ec752 | 1375 | struct aa_profile *profile = NULL; |
3265949f | 1376 | char *ns_name = NULL; |
736ec752 JJ |
1377 | int error; |
1378 | struct aa_ext e = { | |
5ac8c355 JJ |
1379 | .start = udata->data, |
1380 | .end = udata->data + udata->size, | |
1381 | .pos = udata->data, | |
736ec752 JJ |
1382 | }; |
1383 | ||
dd51c848 JJ |
1384 | *ns = NULL; |
1385 | while (e.pos < e.end) { | |
f8eb8a13 | 1386 | void *start; |
dd51c848 JJ |
1387 | error = verify_header(&e, e.pos == e.start, ns); |
1388 | if (error) | |
1389 | goto fail; | |
736ec752 | 1390 | |
f8eb8a13 | 1391 | start = e.pos; |
04dc715e | 1392 | profile = unpack_profile(&e, &ns_name); |
dd51c848 JJ |
1393 | if (IS_ERR(profile)) { |
1394 | error = PTR_ERR(profile); | |
1395 | goto fail; | |
1396 | } | |
1397 | ||
1398 | error = verify_profile(profile); | |
f8eb8a13 JJ |
1399 | if (error) |
1400 | goto fail_profile; | |
1401 | ||
31f75bfe JJ |
1402 | if (aa_g_hash_policy) |
1403 | error = aa_calc_profile_hash(profile, e.version, start, | |
6059f71f | 1404 | e.pos - start); |
f8eb8a13 JJ |
1405 | if (error) |
1406 | goto fail_profile; | |
dd51c848 JJ |
1407 | |
1408 | ent = aa_load_ent_alloc(); | |
1409 | if (!ent) { | |
1410 | error = -ENOMEM; | |
f8eb8a13 | 1411 | goto fail_profile; |
dd51c848 | 1412 | } |
736ec752 | 1413 | |
dd51c848 | 1414 | ent->new = profile; |
04dc715e | 1415 | ent->ns_name = ns_name; |
3265949f | 1416 | ns_name = NULL; |
dd51c848 | 1417 | list_add_tail(&ent->list, lh); |
736ec752 | 1418 | } |
5ac8c355 | 1419 | udata->abi = e.version & K_ABI_MASK; |
31f75bfe JJ |
1420 | if (aa_g_hash_policy) { |
1421 | udata->hash = aa_calc_hash(udata->data, udata->size); | |
1422 | if (IS_ERR(udata->hash)) { | |
1423 | error = PTR_ERR(udata->hash); | |
1424 | udata->hash = NULL; | |
1425 | goto fail; | |
1426 | } | |
5ac8c355 | 1427 | } |
d61c57fd JJ |
1428 | |
1429 | if (aa_g_export_binary) { | |
1430 | error = compress_loaddata(udata); | |
1431 | if (error) | |
1432 | goto fail; | |
1433 | } | |
dd51c848 JJ |
1434 | return 0; |
1435 | ||
f8eb8a13 | 1436 | fail_profile: |
3265949f | 1437 | kfree(ns_name); |
f8eb8a13 JJ |
1438 | aa_put_profile(profile); |
1439 | ||
dd51c848 JJ |
1440 | fail: |
1441 | list_for_each_entry_safe(ent, tmp, lh, list) { | |
1442 | list_del_init(&ent->list); | |
1443 | aa_load_ent_free(ent); | |
1444 | } | |
1445 | ||
1446 | return error; | |
736ec752 | 1447 | } |