xref: /linux/security/apparmor/file.c (revision a1ff5a7d78a036d6c2178ee5acd6ba4946243800)
1b886d83cSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
26380bd8dSJohn Johansen /*
36380bd8dSJohn Johansen  * AppArmor security module
46380bd8dSJohn Johansen  *
56380bd8dSJohn Johansen  * This file contains AppArmor mediation of files
66380bd8dSJohn Johansen  *
76380bd8dSJohn Johansen  * Copyright (C) 1998-2008 Novell/SUSE
86380bd8dSJohn Johansen  * Copyright 2009-2010 Canonical Ltd.
96380bd8dSJohn Johansen  */
106380bd8dSJohn Johansen 
11192ca6b5SJohn Johansen #include <linux/tty.h>
12192ca6b5SJohn Johansen #include <linux/fdtable.h>
13192ca6b5SJohn Johansen #include <linux/file.h>
143cee6079SChristian Brauner #include <linux/fs.h>
153cee6079SChristian Brauner #include <linux/mount.h>
16192ca6b5SJohn Johansen 
176380bd8dSJohn Johansen #include "include/apparmor.h"
186380bd8dSJohn Johansen #include "include/audit.h"
19d8889d49SJohn Johansen #include "include/cred.h"
206380bd8dSJohn Johansen #include "include/file.h"
216380bd8dSJohn Johansen #include "include/match.h"
2256974a6fSJohn Johansen #include "include/net.h"
236380bd8dSJohn Johansen #include "include/path.h"
246380bd8dSJohn Johansen #include "include/policy.h"
25190a9518SJohn Johansen #include "include/label.h"
266380bd8dSJohn Johansen 
map_mask_to_chr_mask(u32 mask)27e53cfe6cSJohn Johansen static u32 map_mask_to_chr_mask(u32 mask)
28e53cfe6cSJohn Johansen {
29e53cfe6cSJohn Johansen 	u32 m = mask & PERMS_CHRS_MASK;
30e53cfe6cSJohn Johansen 
31e53cfe6cSJohn Johansen 	if (mask & AA_MAY_GETATTR)
32e53cfe6cSJohn Johansen 		m |= MAY_READ;
33e53cfe6cSJohn Johansen 	if (mask & (AA_MAY_SETATTR | AA_MAY_CHMOD | AA_MAY_CHOWN))
34e53cfe6cSJohn Johansen 		m |= MAY_WRITE;
35e53cfe6cSJohn Johansen 
36e53cfe6cSJohn Johansen 	return m;
37e53cfe6cSJohn Johansen }
386380bd8dSJohn Johansen 
396380bd8dSJohn Johansen /**
406380bd8dSJohn Johansen  * file_audit_cb - call back for file specific audit fields
416380bd8dSJohn Johansen  * @ab: audit_buffer  (NOT NULL)
426380bd8dSJohn Johansen  * @va: audit struct to audit values of  (NOT NULL)
436380bd8dSJohn Johansen  */
file_audit_cb(struct audit_buffer * ab,void * va)446380bd8dSJohn Johansen static void file_audit_cb(struct audit_buffer *ab, void *va)
456380bd8dSJohn Johansen {
466380bd8dSJohn Johansen 	struct common_audit_data *sa = va;
47bd7bd201SJohn Johansen 	struct apparmor_audit_data *ad = aad(sa);
4890c436a6SJohn Johansen 	kuid_t fsuid = ad->subj_cred ? ad->subj_cred->fsuid : current_fsuid();
49f1d9b23cSRichard Guy Briggs 	char str[10];
506380bd8dSJohn Johansen 
51bd7bd201SJohn Johansen 	if (ad->request & AA_AUDIT_FILE_MASK) {
52f1d9b23cSRichard Guy Briggs 		aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs,
53bd7bd201SJohn Johansen 				    map_mask_to_chr_mask(ad->request));
54f1d9b23cSRichard Guy Briggs 		audit_log_format(ab, " requested_mask=\"%s\"", str);
556380bd8dSJohn Johansen 	}
56bd7bd201SJohn Johansen 	if (ad->denied & AA_AUDIT_FILE_MASK) {
57f1d9b23cSRichard Guy Briggs 		aa_perm_mask_to_str(str, sizeof(str), aa_file_perm_chrs,
58bd7bd201SJohn Johansen 				    map_mask_to_chr_mask(ad->denied));
59f1d9b23cSRichard Guy Briggs 		audit_log_format(ab, " denied_mask=\"%s\"", str);
606380bd8dSJohn Johansen 	}
61bd7bd201SJohn Johansen 	if (ad->request & AA_AUDIT_FILE_MASK) {
622db81452SEric W. Biederman 		audit_log_format(ab, " fsuid=%d",
632db81452SEric W. Biederman 				 from_kuid(&init_user_ns, fsuid));
642db81452SEric W. Biederman 		audit_log_format(ab, " ouid=%d",
65bd7bd201SJohn Johansen 				 from_kuid(&init_user_ns, ad->fs.ouid));
666380bd8dSJohn Johansen 	}
676380bd8dSJohn Johansen 
68bd7bd201SJohn Johansen 	if (ad->peer) {
6998c3d182SJohn Johansen 		audit_log_format(ab, " target=");
70d20f5a1aSJohn Johansen 		aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer,
718ac2ca32SSebastian Andrzej Siewior 				FLAG_VIEW_SUBNS, GFP_KERNEL);
72bd7bd201SJohn Johansen 	} else if (ad->fs.target) {
736380bd8dSJohn Johansen 		audit_log_format(ab, " target=");
74bd7bd201SJohn Johansen 		audit_log_untrustedstring(ab, ad->fs.target);
756380bd8dSJohn Johansen 	}
766380bd8dSJohn Johansen }
776380bd8dSJohn Johansen 
786380bd8dSJohn Johansen /**
796380bd8dSJohn Johansen  * aa_audit_file - handle the auditing of file operations
8090c436a6SJohn Johansen  * @subj_cred: cred of the subject
816380bd8dSJohn Johansen  * @profile: the profile being enforced  (NOT NULL)
826380bd8dSJohn Johansen  * @perms: the permissions computed for the request (NOT NULL)
836380bd8dSJohn Johansen  * @op: operation being mediated
846380bd8dSJohn Johansen  * @request: permissions requested
856380bd8dSJohn Johansen  * @name: name of object being mediated (MAYBE NULL)
866380bd8dSJohn Johansen  * @target: name of target (MAYBE NULL)
8798c3d182SJohn Johansen  * @tlabel: target label (MAY BE NULL)
886380bd8dSJohn Johansen  * @ouid: object uid
896380bd8dSJohn Johansen  * @info: extra information message (MAYBE NULL)
906380bd8dSJohn Johansen  * @error: 0 if operation allowed else failure error code
916380bd8dSJohn Johansen  *
926380bd8dSJohn Johansen  * Returns: %0 or error on failure
936380bd8dSJohn Johansen  */
aa_audit_file(const struct cred * subj_cred,struct aa_profile * profile,struct aa_perms * perms,const char * op,u32 request,const char * name,const char * target,struct aa_label * tlabel,kuid_t ouid,const char * info,int error)9490c436a6SJohn Johansen int aa_audit_file(const struct cred *subj_cred,
9590c436a6SJohn Johansen 		  struct aa_profile *profile, struct aa_perms *perms,
96ef88a7acSJohn Johansen 		  const char *op, u32 request, const char *name,
9798c3d182SJohn Johansen 		  const char *target, struct aa_label *tlabel,
9898c3d182SJohn Johansen 		  kuid_t ouid, const char *info, int error)
996380bd8dSJohn Johansen {
1006380bd8dSJohn Johansen 	int type = AUDIT_APPARMOR_AUTO;
101bd7bd201SJohn Johansen 	DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_TASK, AA_CLASS_FILE, op);
1026380bd8dSJohn Johansen 
10390c436a6SJohn Johansen 	ad.subj_cred = subj_cred;
104bd7bd201SJohn Johansen 	ad.request = request;
105bd7bd201SJohn Johansen 	ad.name = name;
106bd7bd201SJohn Johansen 	ad.fs.target = target;
107bd7bd201SJohn Johansen 	ad.peer = tlabel;
108bd7bd201SJohn Johansen 	ad.fs.ouid = ouid;
109bd7bd201SJohn Johansen 	ad.info = info;
110bd7bd201SJohn Johansen 	ad.error = error;
111bd7bd201SJohn Johansen 	ad.common.u.tsk = NULL;
112ef88a7acSJohn Johansen 
113bd7bd201SJohn Johansen 	if (likely(!ad.error)) {
1146380bd8dSJohn Johansen 		u32 mask = perms->audit;
1156380bd8dSJohn Johansen 
1166380bd8dSJohn Johansen 		if (unlikely(AUDIT_MODE(profile) == AUDIT_ALL))
1176380bd8dSJohn Johansen 			mask = 0xffff;
1186380bd8dSJohn Johansen 
1196380bd8dSJohn Johansen 		/* mask off perms that are not being force audited */
120bd7bd201SJohn Johansen 		ad.request &= mask;
1216380bd8dSJohn Johansen 
122bd7bd201SJohn Johansen 		if (likely(!ad.request))
1236380bd8dSJohn Johansen 			return 0;
1246380bd8dSJohn Johansen 		type = AUDIT_APPARMOR_AUDIT;
1256380bd8dSJohn Johansen 	} else {
1266380bd8dSJohn Johansen 		/* only report permissions that were denied */
127bd7bd201SJohn Johansen 		ad.request = ad.request & ~perms->allow;
128bd7bd201SJohn Johansen 		AA_BUG(!ad.request);
1296380bd8dSJohn Johansen 
130bd7bd201SJohn Johansen 		if (ad.request & perms->kill)
1316380bd8dSJohn Johansen 			type = AUDIT_APPARMOR_KILL;
1326380bd8dSJohn Johansen 
1336380bd8dSJohn Johansen 		/* quiet known rejects, assumes quiet and kill do not overlap */
134bd7bd201SJohn Johansen 		if ((ad.request & perms->quiet) &&
1356380bd8dSJohn Johansen 		    AUDIT_MODE(profile) != AUDIT_NOQUIET &&
1366380bd8dSJohn Johansen 		    AUDIT_MODE(profile) != AUDIT_ALL)
137bd7bd201SJohn Johansen 			ad.request &= ~perms->quiet;
1386380bd8dSJohn Johansen 
139bd7bd201SJohn Johansen 		if (!ad.request)
140bd7bd201SJohn Johansen 			return ad.error;
1416380bd8dSJohn Johansen 	}
1426380bd8dSJohn Johansen 
143bd7bd201SJohn Johansen 	ad.denied = ad.request & ~perms->allow;
144bd7bd201SJohn Johansen 	return aa_audit(type, profile, &ad, file_audit_cb);
1456380bd8dSJohn Johansen }
1466380bd8dSJohn Johansen 
path_name(const char * op,const struct cred * subj_cred,struct aa_label * label,const struct path * path,int flags,char * buffer,const char ** name,struct path_cond * cond,u32 request)14790c436a6SJohn Johansen static int path_name(const char *op, const struct cred *subj_cred,
14890c436a6SJohn Johansen 		     struct aa_label *label,
149aebd873eSJohn Johansen 		     const struct path *path, int flags, char *buffer,
150aebd873eSJohn Johansen 		     const char **name, struct path_cond *cond, u32 request)
151aebd873eSJohn Johansen {
152aebd873eSJohn Johansen 	struct aa_profile *profile;
153aebd873eSJohn Johansen 	const char *info = NULL;
154aebd873eSJohn Johansen 	int error;
155aebd873eSJohn Johansen 
156aebd873eSJohn Johansen 	error = aa_path_name(path, flags, buffer, name, &info,
157aebd873eSJohn Johansen 			     labels_profile(label)->disconnected);
158aebd873eSJohn Johansen 	if (error) {
159aebd873eSJohn Johansen 		fn_for_each_confined(label, profile,
16090c436a6SJohn Johansen 			aa_audit_file(subj_cred,
16190c436a6SJohn Johansen 				      profile, &nullperms, op, request, *name,
162aebd873eSJohn Johansen 				      NULL, NULL, cond->uid, info, error));
163aebd873eSJohn Johansen 		return error;
164aebd873eSJohn Johansen 	}
165aebd873eSJohn Johansen 
166aebd873eSJohn Johansen 	return 0;
167aebd873eSJohn Johansen }
168aebd873eSJohn Johansen 
16976862af5SRandy Dunlap struct aa_perms default_perms = {};
170aebd873eSJohn Johansen /**
171408d53e9SMike Salvatore  * aa_lookup_fperms - convert dfa compressed perms to internal perms
1723175df80SGaosheng Cui  * @file_rules: the aa_policydb to lookup perms for  (NOT NULL)
1736380bd8dSJohn Johansen  * @state: state in dfa
1746380bd8dSJohn Johansen  * @cond:  conditions to consider  (NOT NULL)
1756380bd8dSJohn Johansen  *
176408d53e9SMike Salvatore  * TODO: convert from dfa + state to permission entry
1776380bd8dSJohn Johansen  *
178408d53e9SMike Salvatore  * Returns: a pointer to a file permission set
1796380bd8dSJohn Johansen  */
aa_lookup_fperms(struct aa_policydb * file_rules,aa_state_t state,struct path_cond * cond)18053bdc46fSJohn Johansen struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules,
18133fc95d8SJohn Johansen 				 aa_state_t state, struct path_cond *cond)
1826380bd8dSJohn Johansen {
1837572fea3SJohn Johansen 	unsigned int index = ACCEPT_TABLE(file_rules->dfa)[state];
1846380bd8dSJohn Johansen 
18553bdc46fSJohn Johansen 	if (!(file_rules->perms))
186408d53e9SMike Salvatore 		return &default_perms;
1876380bd8dSJohn Johansen 
188408d53e9SMike Salvatore 	if (uid_eq(current_fsuid(), cond->uid))
1897572fea3SJohn Johansen 		return &(file_rules->perms[index]);
1906380bd8dSJohn Johansen 
1917572fea3SJohn Johansen 	return &(file_rules->perms[index + 1]);
1926380bd8dSJohn Johansen }
1936380bd8dSJohn Johansen 
1946380bd8dSJohn Johansen /**
1956380bd8dSJohn Johansen  * aa_str_perms - find permission that match @name
1963175df80SGaosheng Cui  * @file_rules: the aa_policydb to match against  (NOT NULL)
1973175df80SGaosheng Cui  * @start: state to start matching in
1986380bd8dSJohn Johansen  * @name: string to match against dfa  (NOT NULL)
1996380bd8dSJohn Johansen  * @cond: conditions to consider for permission set computation  (NOT NULL)
2006380bd8dSJohn Johansen  * @perms: Returns - the permissions found when matching @name
2016380bd8dSJohn Johansen  *
2026380bd8dSJohn Johansen  * Returns: the final state in @dfa when beginning @start and walking @name
2036380bd8dSJohn Johansen  */
aa_str_perms(struct aa_policydb * file_rules,aa_state_t start,const char * name,struct path_cond * cond,struct aa_perms * perms)20433fc95d8SJohn Johansen aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start,
2056380bd8dSJohn Johansen 			const char *name, struct path_cond *cond,
2062d679f3cSJohn Johansen 			struct aa_perms *perms)
2076380bd8dSJohn Johansen {
20833fc95d8SJohn Johansen 	aa_state_t state;
209408d53e9SMike Salvatore 	state = aa_dfa_match(file_rules->dfa, start, name);
210408d53e9SMike Salvatore 	*perms = *(aa_lookup_fperms(file_rules, state, cond));
2116380bd8dSJohn Johansen 
2126380bd8dSJohn Johansen 	return state;
2136380bd8dSJohn Johansen }
2146380bd8dSJohn Johansen 
__aa_path_perm(const char * op,const struct cred * subj_cred,struct aa_profile * profile,const char * name,u32 request,struct path_cond * cond,int flags,struct aa_perms * perms)21590c436a6SJohn Johansen static int __aa_path_perm(const char *op, const struct cred *subj_cred,
21690c436a6SJohn Johansen 			  struct aa_profile *profile, const char *name,
21790c436a6SJohn Johansen 			  u32 request, struct path_cond *cond, int flags,
218aebd873eSJohn Johansen 			  struct aa_perms *perms)
2196380bd8dSJohn Johansen {
2201ad22fccSJohn Johansen 	struct aa_ruleset *rules = list_first_entry(&profile->rules,
2211ad22fccSJohn Johansen 						    typeof(*rules), list);
222aebd873eSJohn Johansen 	int e = 0;
223aebd873eSJohn Johansen 
224aebd873eSJohn Johansen 	if (profile_unconfined(profile))
2256380bd8dSJohn Johansen 		return 0;
226*98b824ffSJohn Johansen 	aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],
22753bdc46fSJohn Johansen 		     name, cond, perms);
228aebd873eSJohn Johansen 	if (request & ~perms->allow)
229aebd873eSJohn Johansen 		e = -EACCES;
23090c436a6SJohn Johansen 	return aa_audit_file(subj_cred,
23190c436a6SJohn Johansen 			     profile, perms, op, request, name, NULL, NULL,
232aebd873eSJohn Johansen 			     cond->uid, NULL, e);
233aebd873eSJohn Johansen }
234aebd873eSJohn Johansen 
235aebd873eSJohn Johansen 
profile_path_perm(const char * op,const struct cred * subj_cred,struct aa_profile * profile,const struct path * path,char * buffer,u32 request,struct path_cond * cond,int flags,struct aa_perms * perms)23690c436a6SJohn Johansen static int profile_path_perm(const char *op, const struct cred *subj_cred,
23790c436a6SJohn Johansen 			     struct aa_profile *profile,
238aebd873eSJohn Johansen 			     const struct path *path, char *buffer, u32 request,
239aebd873eSJohn Johansen 			     struct path_cond *cond, int flags,
240aebd873eSJohn Johansen 			     struct aa_perms *perms)
241aebd873eSJohn Johansen {
242aebd873eSJohn Johansen 	const char *name;
243aebd873eSJohn Johansen 	int error;
244aebd873eSJohn Johansen 
245aebd873eSJohn Johansen 	if (profile_unconfined(profile))
246aebd873eSJohn Johansen 		return 0;
247aebd873eSJohn Johansen 
24890c436a6SJohn Johansen 	error = path_name(op, subj_cred, &profile->label, path,
249aebd873eSJohn Johansen 			  flags | profile->path_flags, buffer, &name, cond,
250aebd873eSJohn Johansen 			  request);
251aebd873eSJohn Johansen 	if (error)
252aebd873eSJohn Johansen 		return error;
25390c436a6SJohn Johansen 	return __aa_path_perm(op, subj_cred, profile, name, request, cond,
25490c436a6SJohn Johansen 			      flags, perms);
2556380bd8dSJohn Johansen }
2566380bd8dSJohn Johansen 
2576380bd8dSJohn Johansen /**
2586380bd8dSJohn Johansen  * aa_path_perm - do permissions check & audit for @path
2596380bd8dSJohn Johansen  * @op: operation being checked
26090c436a6SJohn Johansen  * @subj_cred: subject cred
261aebd873eSJohn Johansen  * @label: profile being enforced  (NOT NULL)
2626380bd8dSJohn Johansen  * @path: path to check permissions of  (NOT NULL)
2636380bd8dSJohn Johansen  * @flags: any additional path flags beyond what the profile specifies
2646380bd8dSJohn Johansen  * @request: requested permissions
2656380bd8dSJohn Johansen  * @cond: conditional info for this request  (NOT NULL)
2666380bd8dSJohn Johansen  *
2676380bd8dSJohn Johansen  * Returns: %0 else error if access denied or other error
2686380bd8dSJohn Johansen  */
aa_path_perm(const char * op,const struct cred * subj_cred,struct aa_label * label,const struct path * path,int flags,u32 request,struct path_cond * cond)26990c436a6SJohn Johansen int aa_path_perm(const char *op, const struct cred *subj_cred,
27090c436a6SJohn Johansen 		 struct aa_label *label,
27147f6e5ccSJohn Johansen 		 const struct path *path, int flags, u32 request,
27247f6e5ccSJohn Johansen 		 struct path_cond *cond)
2736380bd8dSJohn Johansen {
2742d679f3cSJohn Johansen 	struct aa_perms perms = {};
275aebd873eSJohn Johansen 	struct aa_profile *profile;
276aebd873eSJohn Johansen 	char *buffer = NULL;
2776380bd8dSJohn Johansen 	int error;
2786380bd8dSJohn Johansen 
279aebd873eSJohn Johansen 	flags |= PATH_DELEGATE_DELETED | (S_ISDIR(cond->mode) ? PATH_IS_DIR :
280aebd873eSJohn Johansen 								0);
281341c1fdaSJohn Johansen 	buffer = aa_get_buffer(false);
282df323337SSebastian Andrzej Siewior 	if (!buffer)
283df323337SSebastian Andrzej Siewior 		return -ENOMEM;
284aebd873eSJohn Johansen 	error = fn_for_each_confined(label, profile,
28590c436a6SJohn Johansen 			profile_path_perm(op, subj_cred, profile, path, buffer,
28690c436a6SJohn Johansen 					  request, cond, flags, &perms));
287aebd873eSJohn Johansen 
288df323337SSebastian Andrzej Siewior 	aa_put_buffer(buffer);
2896380bd8dSJohn Johansen 
2906380bd8dSJohn Johansen 	return error;
2916380bd8dSJohn Johansen }
2926380bd8dSJohn Johansen 
2936380bd8dSJohn Johansen /**
2946380bd8dSJohn Johansen  * xindex_is_subset - helper for aa_path_link
2956380bd8dSJohn Johansen  * @link: link permission set
2966380bd8dSJohn Johansen  * @target: target permission set
2976380bd8dSJohn Johansen  *
2986380bd8dSJohn Johansen  * test target x permissions are equal OR a subset of link x permissions
2996380bd8dSJohn Johansen  * this is done as part of the subset test, where a hardlink must have
3006380bd8dSJohn Johansen  * a subset of permissions that the target has.
3016380bd8dSJohn Johansen  *
302e3798609SZou Wei  * Returns: true if subset else false
3036380bd8dSJohn Johansen  */
xindex_is_subset(u32 link,u32 target)3046380bd8dSJohn Johansen static inline bool xindex_is_subset(u32 link, u32 target)
3056380bd8dSJohn Johansen {
3066380bd8dSJohn Johansen 	if (((link & ~AA_X_UNSAFE) != (target & ~AA_X_UNSAFE)) ||
3076380bd8dSJohn Johansen 	    ((link & AA_X_UNSAFE) && !(target & AA_X_UNSAFE)))
308e3798609SZou Wei 		return false;
3096380bd8dSJohn Johansen 
310e3798609SZou Wei 	return true;
3116380bd8dSJohn Johansen }
3126380bd8dSJohn Johansen 
profile_path_link(const struct cred * subj_cred,struct aa_profile * profile,const struct path * link,char * buffer,const struct path * target,char * buffer2,struct path_cond * cond)31390c436a6SJohn Johansen static int profile_path_link(const struct cred *subj_cred,
31490c436a6SJohn Johansen 			     struct aa_profile *profile,
3158014370fSJohn Johansen 			     const struct path *link, char *buffer,
3168014370fSJohn Johansen 			     const struct path *target, char *buffer2,
3178014370fSJohn Johansen 			     struct path_cond *cond)
3186380bd8dSJohn Johansen {
3191ad22fccSJohn Johansen 	struct aa_ruleset *rules = list_first_entry(&profile->rules,
3201ad22fccSJohn Johansen 						    typeof(*rules), list);
3218014370fSJohn Johansen 	const char *lname, *tname = NULL;
3228014370fSJohn Johansen 	struct aa_perms lperms = {}, perms;
3238014370fSJohn Johansen 	const char *info = NULL;
3246380bd8dSJohn Johansen 	u32 request = AA_MAY_LINK;
32533fc95d8SJohn Johansen 	aa_state_t state;
3266380bd8dSJohn Johansen 	int error;
3276380bd8dSJohn Johansen 
32890c436a6SJohn Johansen 	error = path_name(OP_LINK, subj_cred, &profile->label, link,
32990c436a6SJohn Johansen 			  profile->path_flags,
3308014370fSJohn Johansen 			  buffer, &lname, cond, AA_MAY_LINK);
3316380bd8dSJohn Johansen 	if (error)
3326380bd8dSJohn Johansen 		goto audit;
3336380bd8dSJohn Johansen 
3346380bd8dSJohn Johansen 	/* buffer2 freed below, tname is pointer in buffer2 */
33590c436a6SJohn Johansen 	error = path_name(OP_LINK, subj_cred, &profile->label, target,
33690c436a6SJohn Johansen 			  profile->path_flags,
3378014370fSJohn Johansen 			  buffer2, &tname, cond, AA_MAY_LINK);
3386380bd8dSJohn Johansen 	if (error)
3396380bd8dSJohn Johansen 		goto audit;
3406380bd8dSJohn Johansen 
3416380bd8dSJohn Johansen 	error = -EACCES;
3426380bd8dSJohn Johansen 	/* aa_str_perms - handles the case of the dfa being NULL */
343*98b824ffSJohn Johansen 	state = aa_str_perms(rules->file,
344*98b824ffSJohn Johansen 			     rules->file->start[AA_CLASS_FILE], lname,
3458014370fSJohn Johansen 			     cond, &lperms);
3466380bd8dSJohn Johansen 
3476380bd8dSJohn Johansen 	if (!(lperms.allow & AA_MAY_LINK))
3486380bd8dSJohn Johansen 		goto audit;
3496380bd8dSJohn Johansen 
3506380bd8dSJohn Johansen 	/* test to see if target can be paired with link */
351*98b824ffSJohn Johansen 	state = aa_dfa_null_transition(rules->file->dfa, state);
352*98b824ffSJohn Johansen 	aa_str_perms(rules->file, state, tname, cond, &perms);
3536380bd8dSJohn Johansen 
3546380bd8dSJohn Johansen 	/* force audit/quiet masks for link are stored in the second entry
3556380bd8dSJohn Johansen 	 * in the link pair.
3566380bd8dSJohn Johansen 	 */
3576380bd8dSJohn Johansen 	lperms.audit = perms.audit;
3586380bd8dSJohn Johansen 	lperms.quiet = perms.quiet;
3596380bd8dSJohn Johansen 	lperms.kill = perms.kill;
3606380bd8dSJohn Johansen 
3616380bd8dSJohn Johansen 	if (!(perms.allow & AA_MAY_LINK)) {
3626380bd8dSJohn Johansen 		info = "target restricted";
3638014370fSJohn Johansen 		lperms = perms;
3646380bd8dSJohn Johansen 		goto audit;
3656380bd8dSJohn Johansen 	}
3666380bd8dSJohn Johansen 
3676380bd8dSJohn Johansen 	/* done if link subset test is not required */
3686380bd8dSJohn Johansen 	if (!(perms.allow & AA_LINK_SUBSET))
3696380bd8dSJohn Johansen 		goto done_tests;
3706380bd8dSJohn Johansen 
3718014370fSJohn Johansen 	/* Do link perm subset test requiring allowed permission on link are
3728014370fSJohn Johansen 	 * a subset of the allowed permissions on target.
3736380bd8dSJohn Johansen 	 */
374*98b824ffSJohn Johansen 	aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE],
37553bdc46fSJohn Johansen 		     tname, cond, &perms);
3766380bd8dSJohn Johansen 
3776380bd8dSJohn Johansen 	/* AA_MAY_LINK is not considered in the subset test */
3786380bd8dSJohn Johansen 	request = lperms.allow & ~AA_MAY_LINK;
3796380bd8dSJohn Johansen 	lperms.allow &= perms.allow | AA_MAY_LINK;
3806380bd8dSJohn Johansen 
3816380bd8dSJohn Johansen 	request |= AA_AUDIT_FILE_MASK & (lperms.allow & ~perms.allow);
3826380bd8dSJohn Johansen 	if (request & ~lperms.allow) {
3836380bd8dSJohn Johansen 		goto audit;
3846380bd8dSJohn Johansen 	} else if ((lperms.allow & MAY_EXEC) &&
3856380bd8dSJohn Johansen 		   !xindex_is_subset(lperms.xindex, perms.xindex)) {
3866380bd8dSJohn Johansen 		lperms.allow &= ~MAY_EXEC;
3876380bd8dSJohn Johansen 		request |= MAY_EXEC;
3886380bd8dSJohn Johansen 		info = "link not subset of target";
3896380bd8dSJohn Johansen 		goto audit;
3906380bd8dSJohn Johansen 	}
3916380bd8dSJohn Johansen 
3926380bd8dSJohn Johansen done_tests:
3936380bd8dSJohn Johansen 	error = 0;
3946380bd8dSJohn Johansen 
3956380bd8dSJohn Johansen audit:
39690c436a6SJohn Johansen 	return aa_audit_file(subj_cred,
39790c436a6SJohn Johansen 			     profile, &lperms, OP_LINK, request, lname, tname,
3988014370fSJohn Johansen 			     NULL, cond->uid, info, error);
3998014370fSJohn Johansen }
4008014370fSJohn Johansen 
4018014370fSJohn Johansen /**
4028014370fSJohn Johansen  * aa_path_link - Handle hard link permission check
40390c436a6SJohn Johansen  * @subj_cred: subject cred
4048014370fSJohn Johansen  * @label: the label being enforced  (NOT NULL)
4058014370fSJohn Johansen  * @old_dentry: the target dentry  (NOT NULL)
4068014370fSJohn Johansen  * @new_dir: directory the new link will be created in  (NOT NULL)
4078014370fSJohn Johansen  * @new_dentry: the link being created  (NOT NULL)
4088014370fSJohn Johansen  *
4098014370fSJohn Johansen  * Handle the permission test for a link & target pair.  Permission
4108014370fSJohn Johansen  * is encoded as a pair where the link permission is determined
4118014370fSJohn Johansen  * first, and if allowed, the target is tested.  The target test
4128014370fSJohn Johansen  * is done from the point of the link match (not start of DFA)
4138014370fSJohn Johansen  * making the target permission dependent on the link permission match.
4148014370fSJohn Johansen  *
4158014370fSJohn Johansen  * The subset test if required forces that permissions granted
4168014370fSJohn Johansen  * on link are a subset of the permission granted to target.
4178014370fSJohn Johansen  *
4188014370fSJohn Johansen  * Returns: %0 if allowed else error
4198014370fSJohn Johansen  */
aa_path_link(const struct cred * subj_cred,struct aa_label * label,struct dentry * old_dentry,const struct path * new_dir,struct dentry * new_dentry)42090c436a6SJohn Johansen int aa_path_link(const struct cred *subj_cred,
42190c436a6SJohn Johansen 		 struct aa_label *label, struct dentry *old_dentry,
4228014370fSJohn Johansen 		 const struct path *new_dir, struct dentry *new_dentry)
4238014370fSJohn Johansen {
424c4758fa5SStephen Rothwell 	struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry };
425c4758fa5SStephen Rothwell 	struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry };
4268014370fSJohn Johansen 	struct path_cond cond = {
4278014370fSJohn Johansen 		d_backing_inode(old_dentry)->i_uid,
4288014370fSJohn Johansen 		d_backing_inode(old_dentry)->i_mode
4298014370fSJohn Johansen 	};
4308014370fSJohn Johansen 	char *buffer = NULL, *buffer2 = NULL;
4318014370fSJohn Johansen 	struct aa_profile *profile;
4328014370fSJohn Johansen 	int error;
4338014370fSJohn Johansen 
4348014370fSJohn Johansen 	/* buffer freed below, lname is pointer in buffer */
435341c1fdaSJohn Johansen 	buffer = aa_get_buffer(false);
436341c1fdaSJohn Johansen 	buffer2 = aa_get_buffer(false);
437df323337SSebastian Andrzej Siewior 	error = -ENOMEM;
438df323337SSebastian Andrzej Siewior 	if (!buffer || !buffer2)
439df323337SSebastian Andrzej Siewior 		goto out;
440df323337SSebastian Andrzej Siewior 
4418014370fSJohn Johansen 	error = fn_for_each_confined(label, profile,
44290c436a6SJohn Johansen 			profile_path_link(subj_cred, profile, &link, buffer,
44390c436a6SJohn Johansen 					  &target, buffer2, &cond));
444df323337SSebastian Andrzej Siewior out:
445df323337SSebastian Andrzej Siewior 	aa_put_buffer(buffer);
446df323337SSebastian Andrzej Siewior 	aa_put_buffer(buffer2);
4476380bd8dSJohn Johansen 	return error;
4486380bd8dSJohn Johansen }
4496380bd8dSJohn Johansen 
update_file_ctx(struct aa_file_ctx * fctx,struct aa_label * label,u32 request)450496c9319SJohn Johansen static void update_file_ctx(struct aa_file_ctx *fctx, struct aa_label *label,
451496c9319SJohn Johansen 			    u32 request)
452496c9319SJohn Johansen {
453496c9319SJohn Johansen 	struct aa_label *l, *old;
454496c9319SJohn Johansen 
455496c9319SJohn Johansen 	/* update caching of label on file_ctx */
456496c9319SJohn Johansen 	spin_lock(&fctx->lock);
457496c9319SJohn Johansen 	old = rcu_dereference_protected(fctx->label,
4580fb871ccSLance Roy 					lockdep_is_held(&fctx->lock));
459496c9319SJohn Johansen 	l = aa_label_merge(old, label, GFP_ATOMIC);
460496c9319SJohn Johansen 	if (l) {
461496c9319SJohn Johansen 		if (l != old) {
462496c9319SJohn Johansen 			rcu_assign_pointer(fctx->label, l);
463496c9319SJohn Johansen 			aa_put_label(old);
464496c9319SJohn Johansen 		} else
465496c9319SJohn Johansen 			aa_put_label(l);
466496c9319SJohn Johansen 		fctx->allow |= request;
467496c9319SJohn Johansen 	}
468496c9319SJohn Johansen 	spin_unlock(&fctx->lock);
469496c9319SJohn Johansen }
470496c9319SJohn Johansen 
__file_path_perm(const char * op,const struct cred * subj_cred,struct aa_label * label,struct aa_label * flabel,struct file * file,u32 request,u32 denied,bool in_atomic)47190c436a6SJohn Johansen static int __file_path_perm(const char *op, const struct cred *subj_cred,
47290c436a6SJohn Johansen 			    struct aa_label *label,
473496c9319SJohn Johansen 			    struct aa_label *flabel, struct file *file,
474341c1fdaSJohn Johansen 			    u32 request, u32 denied, bool in_atomic)
475496c9319SJohn Johansen {
476496c9319SJohn Johansen 	struct aa_profile *profile;
477496c9319SJohn Johansen 	struct aa_perms perms = {};
478e67fe633SChristian Brauner 	vfsuid_t vfsuid = i_uid_into_vfsuid(file_mnt_idmap(file),
4795e26a01eSChristian Brauner 					    file_inode(file));
480496c9319SJohn Johansen 	struct path_cond cond = {
4815e26a01eSChristian Brauner 		.uid = vfsuid_into_kuid(vfsuid),
482496c9319SJohn Johansen 		.mode = file_inode(file)->i_mode
483496c9319SJohn Johansen 	};
484496c9319SJohn Johansen 	char *buffer;
485496c9319SJohn Johansen 	int flags, error;
486496c9319SJohn Johansen 
487496c9319SJohn Johansen 	/* revalidation due to label out of date. No revocation at this time */
488496c9319SJohn Johansen 	if (!denied && aa_label_is_subset(flabel, label))
489496c9319SJohn Johansen 		/* TODO: check for revocation on stale profiles */
490496c9319SJohn Johansen 		return 0;
491496c9319SJohn Johansen 
492496c9319SJohn Johansen 	flags = PATH_DELEGATE_DELETED | (S_ISDIR(cond.mode) ? PATH_IS_DIR : 0);
493341c1fdaSJohn Johansen 	buffer = aa_get_buffer(in_atomic);
494df323337SSebastian Andrzej Siewior 	if (!buffer)
495df323337SSebastian Andrzej Siewior 		return -ENOMEM;
496496c9319SJohn Johansen 
497496c9319SJohn Johansen 	/* check every profile in task label not in current cache */
498496c9319SJohn Johansen 	error = fn_for_each_not_in_set(flabel, label, profile,
49990c436a6SJohn Johansen 			profile_path_perm(op, subj_cred, profile,
50090c436a6SJohn Johansen 					  &file->f_path, buffer,
501496c9319SJohn Johansen 					  request, &cond, flags, &perms));
502496c9319SJohn Johansen 	if (denied && !error) {
503496c9319SJohn Johansen 		/*
504496c9319SJohn Johansen 		 * check every profile in file label that was not tested
505496c9319SJohn Johansen 		 * in the initial check above.
506496c9319SJohn Johansen 		 *
507496c9319SJohn Johansen 		 * TODO: cache full perms so this only happens because of
508496c9319SJohn Johansen 		 * conditionals
509496c9319SJohn Johansen 		 * TODO: don't audit here
510496c9319SJohn Johansen 		 */
511496c9319SJohn Johansen 		if (label == flabel)
512496c9319SJohn Johansen 			error = fn_for_each(label, profile,
51390c436a6SJohn Johansen 				profile_path_perm(op, subj_cred,
51490c436a6SJohn Johansen 						  profile, &file->f_path,
515496c9319SJohn Johansen 						  buffer, request, &cond, flags,
516496c9319SJohn Johansen 						  &perms));
517496c9319SJohn Johansen 		else
518496c9319SJohn Johansen 			error = fn_for_each_not_in_set(label, flabel, profile,
51990c436a6SJohn Johansen 				profile_path_perm(op, subj_cred,
52090c436a6SJohn Johansen 						  profile, &file->f_path,
521496c9319SJohn Johansen 						  buffer, request, &cond, flags,
522496c9319SJohn Johansen 						  &perms));
523496c9319SJohn Johansen 	}
524496c9319SJohn Johansen 	if (!error)
525496c9319SJohn Johansen 		update_file_ctx(file_ctx(file), label, request);
526496c9319SJohn Johansen 
527df323337SSebastian Andrzej Siewior 	aa_put_buffer(buffer);
528496c9319SJohn Johansen 
529496c9319SJohn Johansen 	return error;
530496c9319SJohn Johansen }
531496c9319SJohn Johansen 
__file_sock_perm(const char * op,const struct cred * subj_cred,struct aa_label * label,struct aa_label * flabel,struct file * file,u32 request,u32 denied)53290c436a6SJohn Johansen static int __file_sock_perm(const char *op, const struct cred *subj_cred,
53390c436a6SJohn Johansen 			    struct aa_label *label,
53456974a6fSJohn Johansen 			    struct aa_label *flabel, struct file *file,
53556974a6fSJohn Johansen 			    u32 request, u32 denied)
53656974a6fSJohn Johansen {
53756974a6fSJohn Johansen 	struct socket *sock = (struct socket *) file->private_data;
53856974a6fSJohn Johansen 	int error;
53956974a6fSJohn Johansen 
54056974a6fSJohn Johansen 	AA_BUG(!sock);
54156974a6fSJohn Johansen 
54256974a6fSJohn Johansen 	/* revalidation due to label out of date. No revocation at this time */
54356974a6fSJohn Johansen 	if (!denied && aa_label_is_subset(flabel, label))
54456974a6fSJohn Johansen 		return 0;
54556974a6fSJohn Johansen 
54656974a6fSJohn Johansen 	/* TODO: improve to skip profiles cached in flabel */
54790c436a6SJohn Johansen 	error = aa_sock_file_perm(subj_cred, label, op, request, sock);
54856974a6fSJohn Johansen 	if (denied) {
54956974a6fSJohn Johansen 		/* TODO: improve to skip profiles checked above */
55056974a6fSJohn Johansen 		/* check every profile in file label to is cached */
55190c436a6SJohn Johansen 		last_error(error, aa_sock_file_perm(subj_cred, flabel, op,
55290c436a6SJohn Johansen 						    request, sock));
55356974a6fSJohn Johansen 	}
55456974a6fSJohn Johansen 	if (!error)
55556974a6fSJohn Johansen 		update_file_ctx(file_ctx(file), label, request);
55656974a6fSJohn Johansen 
55756974a6fSJohn Johansen 	return error;
55856974a6fSJohn Johansen }
55956974a6fSJohn Johansen 
5606380bd8dSJohn Johansen /**
5616380bd8dSJohn Johansen  * aa_file_perm - do permission revalidation check & audit for @file
5626380bd8dSJohn Johansen  * @op: operation being checked
56390c436a6SJohn Johansen  * @subj_cred: subject cred
564190a9518SJohn Johansen  * @label: label being enforced   (NOT NULL)
5656380bd8dSJohn Johansen  * @file: file to revalidate access permissions on  (NOT NULL)
5666380bd8dSJohn Johansen  * @request: requested permissions
567341c1fdaSJohn Johansen  * @in_atomic: whether allocations need to be done in atomic context
5686380bd8dSJohn Johansen  *
5696380bd8dSJohn Johansen  * Returns: %0 if access allowed else error
5706380bd8dSJohn Johansen  */
aa_file_perm(const char * op,const struct cred * subj_cred,struct aa_label * label,struct file * file,u32 request,bool in_atomic)57190c436a6SJohn Johansen int aa_file_perm(const char *op, const struct cred *subj_cred,
57290c436a6SJohn Johansen 		 struct aa_label *label, struct file *file,
573341c1fdaSJohn Johansen 		 u32 request, bool in_atomic)
5746380bd8dSJohn Johansen {
575190a9518SJohn Johansen 	struct aa_file_ctx *fctx;
576190a9518SJohn Johansen 	struct aa_label *flabel;
577190a9518SJohn Johansen 	u32 denied;
578190a9518SJohn Johansen 	int error = 0;
5796380bd8dSJohn Johansen 
580190a9518SJohn Johansen 	AA_BUG(!label);
581190a9518SJohn Johansen 	AA_BUG(!file);
582190a9518SJohn Johansen 
583190a9518SJohn Johansen 	fctx = file_ctx(file);
584190a9518SJohn Johansen 
585190a9518SJohn Johansen 	rcu_read_lock();
58620d4e80dSJohn Johansen 	flabel  = rcu_dereference(fctx->label);
587190a9518SJohn Johansen 	AA_BUG(!flabel);
588190a9518SJohn Johansen 
589190a9518SJohn Johansen 	/* revalidate access, if task is unconfined, or the cached cred
590190a9518SJohn Johansen 	 * doesn't match or if the request is for more permissions than
591190a9518SJohn Johansen 	 * was granted.
592190a9518SJohn Johansen 	 *
593190a9518SJohn Johansen 	 * Note: the test for !unconfined(flabel) is to handle file
594190a9518SJohn Johansen 	 *       delegation from unconfined tasks
595190a9518SJohn Johansen 	 */
596190a9518SJohn Johansen 	denied = request & ~fctx->allow;
597190a9518SJohn Johansen 	if (unconfined(label) || unconfined(flabel) ||
59820d4e80dSJohn Johansen 	    (!denied && aa_label_is_subset(flabel, label))) {
59920d4e80dSJohn Johansen 		rcu_read_unlock();
600190a9518SJohn Johansen 		goto done;
60120d4e80dSJohn Johansen 	}
602190a9518SJohn Johansen 
60320d4e80dSJohn Johansen 	flabel  = aa_get_newest_label(flabel);
60420d4e80dSJohn Johansen 	rcu_read_unlock();
605190a9518SJohn Johansen 	/* TODO: label cross check */
606190a9518SJohn Johansen 
607190a9518SJohn Johansen 	if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry))
60890c436a6SJohn Johansen 		error = __file_path_perm(op, subj_cred, label, flabel, file,
60990c436a6SJohn Johansen 					 request, denied, in_atomic);
610190a9518SJohn Johansen 
61156974a6fSJohn Johansen 	else if (S_ISSOCK(file_inode(file)->i_mode))
61290c436a6SJohn Johansen 		error = __file_sock_perm(op, subj_cred, label, flabel, file,
61390c436a6SJohn Johansen 					 request, denied);
614bce4e7e9SJohn Johansen 	aa_put_label(flabel);
61520d4e80dSJohn Johansen 
61620d4e80dSJohn Johansen done:
617190a9518SJohn Johansen 	return error;
6186380bd8dSJohn Johansen }
619192ca6b5SJohn Johansen 
revalidate_tty(const struct cred * subj_cred,struct aa_label * label)62090c436a6SJohn Johansen static void revalidate_tty(const struct cred *subj_cred, struct aa_label *label)
621192ca6b5SJohn Johansen {
622192ca6b5SJohn Johansen 	struct tty_struct *tty;
623192ca6b5SJohn Johansen 	int drop_tty = 0;
624192ca6b5SJohn Johansen 
625192ca6b5SJohn Johansen 	tty = get_current_tty();
626192ca6b5SJohn Johansen 	if (!tty)
627192ca6b5SJohn Johansen 		return;
628192ca6b5SJohn Johansen 
629192ca6b5SJohn Johansen 	spin_lock(&tty->files_lock);
630192ca6b5SJohn Johansen 	if (!list_empty(&tty->tty_files)) {
631192ca6b5SJohn Johansen 		struct tty_file_private *file_priv;
632192ca6b5SJohn Johansen 		struct file *file;
633192ca6b5SJohn Johansen 		/* TODO: Revalidate access to controlling tty. */
634192ca6b5SJohn Johansen 		file_priv = list_first_entry(&tty->tty_files,
635192ca6b5SJohn Johansen 					     struct tty_file_private, list);
636192ca6b5SJohn Johansen 		file = file_priv->file;
637192ca6b5SJohn Johansen 
63890c436a6SJohn Johansen 		if (aa_file_perm(OP_INHERIT, subj_cred, label, file,
63990c436a6SJohn Johansen 				 MAY_READ | MAY_WRITE, IN_ATOMIC))
640192ca6b5SJohn Johansen 			drop_tty = 1;
641192ca6b5SJohn Johansen 	}
642192ca6b5SJohn Johansen 	spin_unlock(&tty->files_lock);
643192ca6b5SJohn Johansen 	tty_kref_put(tty);
644192ca6b5SJohn Johansen 
645192ca6b5SJohn Johansen 	if (drop_tty)
646192ca6b5SJohn Johansen 		no_tty();
647192ca6b5SJohn Johansen }
648192ca6b5SJohn Johansen 
64990c436a6SJohn Johansen struct cred_label {
65090c436a6SJohn Johansen 	const struct cred *cred;
65190c436a6SJohn Johansen 	struct aa_label *label;
65290c436a6SJohn Johansen };
65390c436a6SJohn Johansen 
match_file(const void * p,struct file * file,unsigned int fd)654192ca6b5SJohn Johansen static int match_file(const void *p, struct file *file, unsigned int fd)
655192ca6b5SJohn Johansen {
65690c436a6SJohn Johansen 	struct cred_label *cl = (struct cred_label *)p;
657192ca6b5SJohn Johansen 
65890c436a6SJohn Johansen 	if (aa_file_perm(OP_INHERIT, cl->cred, cl->label, file,
65990c436a6SJohn Johansen 			 aa_map_file_to_perms(file), IN_ATOMIC))
660192ca6b5SJohn Johansen 		return fd + 1;
661192ca6b5SJohn Johansen 	return 0;
662192ca6b5SJohn Johansen }
663192ca6b5SJohn Johansen 
664192ca6b5SJohn Johansen 
665192ca6b5SJohn Johansen /* based on selinux's flush_unauthorized_files */
aa_inherit_files(const struct cred * cred,struct files_struct * files)666192ca6b5SJohn Johansen void aa_inherit_files(const struct cred *cred, struct files_struct *files)
667192ca6b5SJohn Johansen {
668637f688dSJohn Johansen 	struct aa_label *label = aa_get_newest_cred_label(cred);
66990c436a6SJohn Johansen 	struct cred_label cl = {
67090c436a6SJohn Johansen 		.cred = cred,
67190c436a6SJohn Johansen 		.label = label,
67290c436a6SJohn Johansen 	};
673192ca6b5SJohn Johansen 	struct file *devnull = NULL;
674192ca6b5SJohn Johansen 	unsigned int n;
675192ca6b5SJohn Johansen 
67690c436a6SJohn Johansen 	revalidate_tty(cred, label);
677192ca6b5SJohn Johansen 
678192ca6b5SJohn Johansen 	/* Revalidate access to inherited open files. */
67990c436a6SJohn Johansen 	n = iterate_fd(files, 0, match_file, &cl);
680192ca6b5SJohn Johansen 	if (!n) /* none found? */
681192ca6b5SJohn Johansen 		goto out;
682192ca6b5SJohn Johansen 
683192ca6b5SJohn Johansen 	devnull = dentry_open(&aa_null, O_RDWR, cred);
684192ca6b5SJohn Johansen 	if (IS_ERR(devnull))
685192ca6b5SJohn Johansen 		devnull = NULL;
686192ca6b5SJohn Johansen 	/* replace all the matching ones with this */
687192ca6b5SJohn Johansen 	do {
688192ca6b5SJohn Johansen 		replace_fd(n - 1, devnull, 0);
68990c436a6SJohn Johansen 	} while ((n = iterate_fd(files, n, match_file, &cl)) != 0);
690192ca6b5SJohn Johansen 	if (devnull)
691192ca6b5SJohn Johansen 		fput(devnull);
692192ca6b5SJohn Johansen out:
693637f688dSJohn Johansen 	aa_put_label(label);
694192ca6b5SJohn Johansen }
695