aboutsummaryrefslogtreecommitdiffstats
path: root/security/apparmor/file.c
diff options
context:
space:
mode:
authorJohn Johansen <john.johansen@canonical.com>2010-07-29 17:48:04 -0400
committerJames Morris <jmorris@namei.org>2010-08-02 01:35:14 -0400
commit6380bd8ddf613b29f478396308b591867d401de4 (patch)
tree6d8fc9356a652f8452ccf49e7f79cc700cc2768d /security/apparmor/file.c
parent63e2b423771ab0bc7ad4d407f3f6517c6d05cdc0 (diff)
AppArmor: file enforcement routines
AppArmor does files enforcement via pathname matching. Matching is done at file open using a dfa match engine. Permission is against the final file object not parent directories, ie. the traversal of directories as part of the file match is implicitly allowed. In the case of nonexistant files (creation) permissions are checked against the target file not the directory. eg. In case of creating the file /dir/new, permissions are checked against the match /dir/new not against /dir/. The permissions for matches are currently stored in the dfa accept table, but this will change to allow for dfa reuse and also to allow for sharing of wider accept states. Signed-off-by: John Johansen <john.johansen@canonical.com> Signed-off-by: James Morris <jmorris@namei.org>
Diffstat (limited to 'security/apparmor/file.c')
-rw-r--r--security/apparmor/file.c457
1 files changed, 457 insertions, 0 deletions
diff --git a/security/apparmor/file.c b/security/apparmor/file.c
new file mode 100644
index 000000000000..7312db741219
--- /dev/null
+++ b/security/apparmor/file.c
@@ -0,0 +1,457 @@
1/*
2 * AppArmor security module
3 *
4 * This file contains AppArmor mediation of files
5 *
6 * Copyright (C) 1998-2008 Novell/SUSE
7 * Copyright 2009-2010 Canonical Ltd.
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation, version 2 of the
12 * License.
13 */
14
15#include "include/apparmor.h"
16#include "include/audit.h"
17#include "include/file.h"
18#include "include/match.h"
19#include "include/path.h"
20#include "include/policy.h"
21
22struct file_perms nullperms;
23
24
25/**
26 * audit_file_mask - convert mask to permission string
27 * @buffer: buffer to write string to (NOT NULL)
28 * @mask: permission mask to convert
29 */
30static void audit_file_mask(struct audit_buffer *ab, u32 mask)
31{
32 char str[10];
33
34 char *m = str;
35
36 if (mask & AA_EXEC_MMAP)
37 *m++ = 'm';
38 if (mask & (MAY_READ | AA_MAY_META_READ))
39 *m++ = 'r';
40 if (mask & (MAY_WRITE | AA_MAY_META_WRITE | AA_MAY_CHMOD |
41 AA_MAY_CHOWN))
42 *m++ = 'w';
43 else if (mask & MAY_APPEND)
44 *m++ = 'a';
45 if (mask & AA_MAY_CREATE)
46 *m++ = 'c';
47 if (mask & AA_MAY_DELETE)
48 *m++ = 'd';
49 if (mask & AA_MAY_LINK)
50 *m++ = 'l';
51 if (mask & AA_MAY_LOCK)
52 *m++ = 'k';
53 if (mask & MAY_EXEC)
54 *m++ = 'x';
55 *m = '\0';
56
57 audit_log_string(ab, str);
58}
59
60/**
61 * file_audit_cb - call back for file specific audit fields
62 * @ab: audit_buffer (NOT NULL)
63 * @va: audit struct to audit values of (NOT NULL)
64 */
65static void file_audit_cb(struct audit_buffer *ab, void *va)
66{
67 struct common_audit_data *sa = va;
68 uid_t fsuid = current_fsuid();
69
70 if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) {
71 audit_log_format(ab, " requested_mask=");
72 audit_file_mask(ab, sa->aad.fs.request);
73 }
74 if (sa->aad.fs.denied & AA_AUDIT_FILE_MASK) {
75 audit_log_format(ab, " denied_mask=");
76 audit_file_mask(ab, sa->aad.fs.denied);
77 }
78 if (sa->aad.fs.request & AA_AUDIT_FILE_MASK) {
79 audit_log_format(ab, " fsuid=%d", fsuid);
80 audit_log_format(ab, " ouid=%d", sa->aad.fs.ouid);
81 }
82
83 if (sa->aad.fs.target) {
84 audit_log_format(ab, " target=");
85 audit_log_untrustedstring(ab, sa->aad.fs.target);
86 }
87}
88
89/**
90 * aa_audit_file - handle the auditing of file operations
91 * @profile: the profile being enforced (NOT NULL)
92 * @perms: the permissions computed for the request (NOT NULL)
93 * @gfp: allocation flags
94 * @op: operation being mediated
95 * @request: permissions requested
96 * @name: name of object being mediated (MAYBE NULL)
97 * @target: name of target (MAYBE NULL)
98 * @ouid: object uid
99 * @info: extra information message (MAYBE NULL)
100 * @error: 0 if operation allowed else failure error code
101 *
102 * Returns: %0 or error on failure
103 */
104int aa_audit_file(struct aa_profile *profile, struct file_perms *perms,
105 gfp_t gfp, int op, u32 request, const char *name,
106 const char *target, uid_t ouid, const char *info, int error)
107{
108 int type = AUDIT_APPARMOR_AUTO;
109 struct common_audit_data sa;
110 COMMON_AUDIT_DATA_INIT(&sa, NONE);
111 sa.aad.op = op,
112 sa.aad.fs.request = request;
113 sa.aad.name = name;
114 sa.aad.fs.target = target;
115 sa.aad.fs.ouid = ouid;
116 sa.aad.info = info;
117 sa.aad.error = error;
118
119 if (likely(!sa.aad.error)) {
120 u32 mask = perms->audit;
121
122 if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
123 mask = 0xffff;
124
125 /* mask off perms that are not being force audited */
126 sa.aad.fs.request &= mask;
127
128 if (likely(!sa.aad.fs.request))
129 return 0;
130 type = AUDIT_APPARMOR_AUDIT;
131 } else {
132 /* only report permissions that were denied */
133 sa.aad.fs.request = sa.aad.fs.request & ~perms->allow;
134
135 if (sa.aad.fs.request & perms->kill)
136 type = AUDIT_APPARMOR_KILL;
137
138 /* quiet known rejects, assumes quiet and kill do not overlap */
139 if ((sa.aad.fs.request & perms->quiet) &&
140 AUDIT_MODE(profile) != AUDIT_NOQUIET &&
141 AUDIT_MODE(profile) != AUDIT_ALL)
142 sa.aad.fs.request &= ~perms->quiet;
143
144 if (!sa.aad.fs.request)
145 return COMPLAIN_MODE(profile) ? 0 : sa.aad.error;
146 }
147
148 sa.aad.fs.denied = sa.aad.fs.request & ~perms->allow;
149 return aa_audit(type, profile, gfp, &sa, file_audit_cb);
150}
151
152/**
153 * map_old_perms - map old file perms layout to the new layout
154 * @old: permission set in old mapping
155 *
156 * Returns: new permission mapping
157 */
158static u32 map_old_perms(u32 old)
159{
160 u32 new = old & 0xf;
161 if (old & MAY_READ)
162 new |= AA_MAY_META_READ;
163 if (old & MAY_WRITE)
164 new |= AA_MAY_META_WRITE | AA_MAY_CREATE | AA_MAY_DELETE |
165 AA_MAY_CHMOD | AA_MAY_CHOWN;
166 if (old & 0x10)
167 new |= AA_MAY_LINK;
168 /* the old mapping lock and link_subset flags where overlaid
169 * and use was determined by part of a pair that they were in
170 */
171 if (old & 0x20)
172 new |= AA_MAY_LOCK | AA_LINK_SUBSET;
173 if (old & 0x40) /* AA_EXEC_MMAP */
174 new |= AA_EXEC_MMAP;
175
176 new |= AA_MAY_META_READ;
177
178 return new;
179}
180
181/**
182 * compute_perms - convert dfa compressed perms to internal perms
183 * @dfa: dfa to compute perms for (NOT NULL)
184 * @state: state in dfa
185 * @cond: conditions to consider (NOT NULL)
186 *
187 * TODO: convert from dfa + state to permission entry, do computation conversion
188 * at load time.
189 *
190 * Returns: computed permission set
191 */
192static struct file_perms compute_perms(struct aa_dfa *dfa, unsigned int state,
193 struct path_cond *cond)
194{
195 struct file_perms perms;
196
197 /* FIXME: change over to new dfa format
198 * currently file perms are encoded in the dfa, new format
199 * splits the permissions from the dfa. This mapping can be
200 * done at profile load
201 */
202 perms.kill = 0;
203
204 if (current_fsuid() == cond->uid) {
205 perms.allow = map_old_perms(dfa_user_allow(dfa, state));
206 perms.audit = map_old_perms(dfa_user_audit(dfa, state));
207 perms.quiet = map_old_perms(dfa_user_quiet(dfa, state));
208 perms.xindex = dfa_user_xindex(dfa, state);
209 } else {
210 perms.allow = map_old_perms(dfa_other_allow(dfa, state));
211 perms.audit = map_old_perms(dfa_other_audit(dfa, state));
212 perms.quiet = map_old_perms(dfa_other_quiet(dfa, state));
213 perms.xindex = dfa_other_xindex(dfa, state);
214 }
215
216 /* change_profile wasn't determined by ownership in old mapping */
217 if (ACCEPT_TABLE(dfa)[state] & 0x80000000)
218 perms.allow |= AA_MAY_CHANGE_PROFILE;
219
220 return perms;
221}
222
223/**
224 * aa_str_perms - find permission that match @name
225 * @dfa: to match against (MAYBE NULL)
226 * @state: state to start matching in
227 * @name: string to match against dfa (NOT NULL)
228 * @cond: conditions to consider for permission set computation (NOT NULL)
229 * @perms: Returns - the permissions found when matching @name
230 *
231 * Returns: the final state in @dfa when beginning @start and walking @name
232 */
233unsigned int aa_str_perms(struct aa_dfa *dfa, unsigned int start,
234 const char *name, struct path_cond *cond,
235 struct file_perms *perms)
236{
237 unsigned int state;
238 if (!dfa) {
239 *perms = nullperms;
240 return DFA_NOMATCH;
241 }
242
243 state = aa_dfa_match(dfa, start, name);
244 *perms = compute_perms(dfa, state, cond);
245
246 return state;
247}
248
249/**
250 * is_deleted - test if a file has been completely unlinked
251 * @dentry: dentry of file to test for deletion (NOT NULL)
252 *
253 * Returns: %1 if deleted else %0
254 */
255static inline bool is_deleted(struct dentry *dentry)
256{
257 if (d_unlinked(dentry) && dentry->d_inode->i_nlink == 0)
258 return 1;
259 return 0;
260}
261
262/**
263 * aa_path_perm - do permissions check & audit for @path
264 * @op: operation being checked
265 * @profile: profile being enforced (NOT NULL)
266 * @path: path to check permissions of (NOT NULL)
267 * @flags: any additional path flags beyond what the profile specifies
268 * @request: requested permissions
269 * @cond: conditional info for this request (NOT NULL)
270 *
271 * Returns: %0 else error if access denied or other error
272 */
273int aa_path_perm(int op, struct aa_profile *profile, struct path *path,
274 int flags, u32 request, struct path_cond *cond)
275{
276 char *buffer = NULL;
277 struct file_perms perms = {};
278 const char *name, *info = NULL;
279 int error;
280
281 flags |= profile->path_flags | (S_ISDIR(cond->mode) ? PATH_IS_DIR : 0);
282 error = aa_get_name(path, flags, &buffer, &name);
283 if (error) {
284 if (error == -ENOENT && is_deleted(path->dentry)) {
285 /* Access to open files that are deleted are
286 * give a pass (implicit delegation)
287 */
288 error = 0;
289 perms.allow = request;
290 } else if (error == -ENOENT)
291 info = "Failed name lookup - deleted entry";
292 else if (error == -ESTALE)
293 info = "Failed name lookup - disconnected path";
294 else if (error == -ENAMETOOLONG)
295 info = "Failed name lookup - name too long";
296 else
297 info = "Failed name lookup";
298 } else {
299 aa_str_perms(profile->file.dfa, profile->file.start, name, cond,
300 &perms);
301 if (request & ~perms.allow)
302 error = -EACCES;
303 }
304 error = aa_audit_file(profile, &perms, GFP_KERNEL, op, request, name,
305 NULL, cond->uid, info, error);
306 kfree(buffer);
307
308 return error;
309}
310
311/**
312 * xindex_is_subset - helper for aa_path_link
313 * @link: link permission set
314 * @target: target permission set
315 *
316 * test target x permissions are equal OR a subset of link x permissions
317 * this is done as part of the subset test, where a hardlink must have
318 * a subset of permissions that the target has.
319 *
320 * Returns: %1 if subset else %0
321 */
322static inline bool xindex_is_subset(u32 link, u32 target)
323{
324 if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) ||
325 ((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE)))
326 return 0;
327
328 return 1;
329}
330
331/**
332 * aa_path_link - Handle hard link permission check
333 * @profile: the profile being enforced (NOT NULL)
334 * @old_dentry: the target dentry (NOT NULL)
335 * @new_dir: directory the new link will be created in (NOT NULL)
336 * @new_dentry: the link being created (NOT NULL)
337 *
338 * Handle the permission test for a link & target pair. Permission
339 * is encoded as a pair where the link permission is determined
340 * first, and if allowed, the target is tested. The target test
341 * is done from the point of the link match (not start of DFA)
342 * making the target permission dependent on the link permission match.
343 *
344 * The subset test if required forces that permissions granted
345 * on link are a subset of the permission granted to target.
346 *
347 * Returns: %0 if allowed else error
348 */
349int aa_path_link(struct aa_profile *profile, struct dentry *old_dentry,
350 struct path *new_dir, struct dentry *new_dentry)
351{
352 struct path link = { new_dir->mnt, new_dentry };
353 struct path target = { new_dir->mnt, old_dentry };
354 struct path_cond cond = {
355 old_dentry->d_inode->i_uid,
356 old_dentry->d_inode->i_mode
357 };
358 char *buffer = NULL, *buffer2 = NULL;
359 const char *lname, *tname = NULL, *info = NULL;
360 struct file_perms lperms, perms;
361 u32 request = AA_MAY_LINK;
362 unsigned int state;
363 int error;
364
365 lperms = nullperms;
366
367 /* buffer freed below, lname is pointer in buffer */
368 error = aa_get_name(&link, profile->path_flags, &buffer, &lname);
369 if (error)
370 goto audit;
371
372 /* buffer2 freed below, tname is pointer in buffer2 */
373 error = aa_get_name(&target, profile->path_flags, &buffer2, &tname);
374 if (error)
375 goto audit;
376
377 error = -EACCES;
378 /* aa_str_perms - handles the case of the dfa being NULL */
379 state = aa_str_perms(profile->file.dfa, profile->file.start, lname,
380 &cond, &lperms);
381
382 if (!(lperms.allow & AA_MAY_LINK))
383 goto audit;
384
385 /* test to see if target can be paired with link */
386 state = aa_dfa_null_transition(profile->file.dfa, state);
387 aa_str_perms(profile->file.dfa, state, tname, &cond, &perms);
388
389 /* force audit/quiet masks for link are stored in the second entry
390 * in the link pair.
391 */
392 lperms.audit = perms.audit;
393 lperms.quiet = perms.quiet;
394 lperms.kill = perms.kill;
395
396 if (!(perms.allow & AA_MAY_LINK)) {
397 info = "target restricted";
398 goto audit;
399 }
400
401 /* done if link subset test is not required */
402 if (!(perms.allow & AA_LINK_SUBSET))
403 goto done_tests;
404
405 /* Do link perm subset test requiring allowed permission on link are a
406 * subset of the allowed permissions on target.
407 */
408 aa_str_perms(profile->file.dfa, profile->file.start, tname, &cond,
409 &perms);
410
411 /* AA_MAY_LINK is not considered in the subset test */
412 request = lperms.allow & ~AA_MAY_LINK;
413 lperms.allow &= perms.allow | AA_MAY_LINK;
414
415 request |= AA_AUDIT_FILE_MASK & (lperms.allow & ~perms.allow);
416 if (request & ~lperms.allow) {
417 goto audit;
418 } else if ((lperms.allow & MAY_EXEC) &&
419 !xindex_is_subset(lperms.xindex, perms.xindex)) {
420 lperms.allow &= ~MAY_EXEC;
421 request |= MAY_EXEC;
422 info = "link not subset of target";
423 goto audit;
424 }
425
426done_tests:
427 error = 0;
428
429audit:
430 error = aa_audit_file(profile, &lperms, GFP_KERNEL, OP_LINK, request,
431 lname, tname, cond.uid, info, error);
432 kfree(buffer);
433 kfree(buffer2);
434
435 return error;
436}
437
438/**
439 * aa_file_perm - do permission revalidation check & audit for @file
440 * @op: operation being checked
441 * @profile: profile being enforced (NOT NULL)
442 * @file: file to revalidate access permissions on (NOT NULL)
443 * @request: requested permissions
444 *
445 * Returns: %0 if access allowed else error
446 */
447int aa_file_perm(int op, struct aa_profile *profile, struct file *file,
448 u32 request)
449{
450 struct path_cond cond = {
451 .uid = file->f_path.dentry->d_inode->i_uid,
452 .mode = file->f_path.dentry->d_inode->i_mode
453 };
454
455 return aa_path_perm(op, profile, &file->f_path, PATH_DELEGATE_DELETED,
456 request, &cond);
457}