xref: /linux/security/apparmor/path.c (revision 69050f8d6d075dc01af7a5f2f550a8067510366f)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * AppArmor security module
4  *
5  * This file contains AppArmor function for pathnames
6  *
7  * Copyright (C) 1998-2008 Novell/SUSE
8  * Copyright 2009-2010 Canonical Ltd.
9  */
10 
11 #include <linux/magic.h>
12 #include <linux/mount.h>
13 #include <linux/namei.h>
14 #include <linux/nsproxy.h>
15 #include <linux/path.h>
16 #include <linux/sched.h>
17 #include <linux/slab.h>
18 #include <linux/fs_struct.h>
19 
20 #include "include/apparmor.h"
21 #include "include/path.h"
22 #include "include/policy.h"
23 
24 /* modified from dcache.c */
25 static int prepend(char **buffer, int buflen, const char *str, int namelen)
26 {
27 	buflen -= namelen;
28 	if (buflen < 0)
29 		return -ENAMETOOLONG;
30 	*buffer -= namelen;
31 	memcpy(*buffer, str, namelen);
32 	return 0;
33 }
34 
35 #define CHROOT_NSCONNECT (PATH_CHROOT_REL | PATH_CHROOT_NSCONNECT)
36 
37 /* If the path is not connected to the expected root,
38  * check if it is a sysctl and handle specially else remove any
39  * leading / that __d_path may have returned.
40  * Unless
41  *     specifically directed to connect the path,
42  * OR
43  *     if in a chroot and doing chroot relative paths and the path
44  *     resolves to the namespace root (would be connected outside
45  *     of chroot) and specifically directed to connect paths to
46  *     namespace root.
47  */
48 static int disconnect(const struct path *path, char *buf, char **name,
49 		      int flags, const char *disconnected)
50 {
51 	int error = 0;
52 
53 	if (!(flags & PATH_CONNECT_PATH) &&
54 	    !(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
55 	      our_mnt(path->mnt))) {
56 		/* disconnected path, don't return pathname starting
57 		 * with '/'
58 		 */
59 		error = -EACCES;
60 		if (**name == '/')
61 			*name = *name + 1;
62 	} else {
63 		if (**name != '/')
64 			/* CONNECT_PATH with missing root */
65 			error = prepend(name, *name - buf, "/", 1);
66 		if (!error && disconnected)
67 			error = prepend(name, *name - buf, disconnected,
68 					strlen(disconnected));
69 	}
70 
71 	return error;
72 }
73 
74 /**
75  * d_namespace_path - lookup a name associated with a given path
76  * @path: path to lookup  (NOT NULL)
77  * @buf:  buffer to store path to  (NOT NULL)
78  * @name: Returns - pointer for start of path name with in @buf (NOT NULL)
79  * @flags: flags controlling path lookup
80  * @disconnected: string to prefix to disconnected paths
81  *
82  * Handle path name lookup.
83  *
84  * Returns: %0 else error code if path lookup fails
85  *          When no error the path name is returned in @name which points to
86  *          a position in @buf
87  */
88 static int d_namespace_path(const struct path *path, char *buf, char **name,
89 			    int flags, const char *disconnected)
90 {
91 	char *res;
92 	int error = 0;
93 	int connected = 1;
94 	int isdir = (flags & PATH_IS_DIR) ? 1 : 0;
95 	int buflen = aa_g_path_max - isdir;
96 
97 	if (path->mnt->mnt_flags & MNT_INTERNAL) {
98 		/* it's not mounted anywhere */
99 		res = dentry_path(path->dentry, buf, buflen);
100 		*name = res;
101 		if (IS_ERR(res)) {
102 			*name = buf;
103 			return PTR_ERR(res);
104 		}
105 		if (path->dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
106 		    strncmp(*name, "/sys/", 5) == 0) {
107 			/* TODO: convert over to using a per namespace
108 			 * control instead of hard coded /proc
109 			 */
110 			error = prepend(name, *name - buf, "/proc", 5);
111 			goto out;
112 		} else
113 			error = disconnect(path, buf, name, flags,
114 					   disconnected);
115 		goto out;
116 	}
117 
118 	/* resolve paths relative to chroot?*/
119 	if (flags & PATH_CHROOT_REL) {
120 		struct path root;
121 		get_fs_root(current->fs, &root);
122 		res = __d_path(path, &root, buf, buflen);
123 		path_put(&root);
124 	} else {
125 		res = d_absolute_path(path, buf, buflen);
126 		if (!our_mnt(path->mnt))
127 			connected = 0;
128 	}
129 
130 	/* handle error conditions - and still allow a partial path to
131 	 * be returned.
132 	 */
133 	if (IS_ERR_OR_NULL(res)) {
134 		if (PTR_ERR(res) == -ENAMETOOLONG) {
135 			error = -ENAMETOOLONG;
136 			*name = buf;
137 			goto out;
138 		}
139 		connected = 0;
140 		res = dentry_path_raw(path->dentry, buf, buflen);
141 		if (IS_ERR(res)) {
142 			error = PTR_ERR(res);
143 			*name = buf;
144 			goto out;
145 		}
146 	} else if (!our_mnt(path->mnt))
147 		connected = 0;
148 
149 	*name = res;
150 
151 	if (!connected)
152 		error = disconnect(path, buf, name, flags, disconnected);
153 
154 	/* Handle two cases:
155 	 * 1. A deleted dentry && profile is not allowing mediation of deleted
156 	 * 2. On some filesystems, newly allocated dentries appear to the
157 	 *    security_path hooks as a deleted dentry except without an inode
158 	 *    allocated.
159 	 */
160 	if (d_unlinked(path->dentry) && d_is_positive(path->dentry) &&
161 	    !(flags & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))) {
162 			error = -ENOENT;
163 			goto out;
164 	}
165 
166 out:
167 	/* Append "/" to directory paths, except for root "/" which
168 	 * already ends in a slash.
169 	 */
170 	if (!error && isdir) {
171 		bool is_root = (*name)[0] == '/' && (*name)[1] == '\0';
172 
173 		if (!is_root)
174 			buf[aa_g_path_max - 2] = '/';
175 	}
176 
177 	return error;
178 }
179 
180 /**
181  * aa_path_name - get the pathname to a buffer ensure dir / is appended
182  * @path: path the file  (NOT NULL)
183  * @flags: flags controlling path name generation
184  * @buffer: buffer to put name in (NOT NULL)
185  * @name: Returns - the generated path name if !error (NOT NULL)
186  * @info: Returns - information on why the path lookup failed (MAYBE NULL)
187  * @disconnected: string to prepend to disconnected paths
188  *
189  * @name is a pointer to the beginning of the pathname (which usually differs
190  * from the beginning of the buffer), or NULL.  If there is an error @name
191  * may contain a partial or invalid name that can be used for audit purposes,
192  * but it can not be used for mediation.
193  *
194  * We need PATH_IS_DIR to indicate whether the file is a directory or not
195  * because the file may not yet exist, and so we cannot check the inode's
196  * file type.
197  *
198  * Returns: %0 else error code if could retrieve name
199  */
200 int aa_path_name(const struct path *path, int flags, char *buffer,
201 		 const char **name, const char **info, const char *disconnected)
202 {
203 	char *str = NULL;
204 	int error = d_namespace_path(path, buffer, &str, flags, disconnected);
205 
206 	if (info && error) {
207 		if (error == -ENOENT)
208 			*info = "Failed name lookup - deleted entry";
209 		else if (error == -EACCES)
210 			*info = "Failed name lookup - disconnected path";
211 		else if (error == -ENAMETOOLONG)
212 			*info = "Failed name lookup - name too long";
213 		else
214 			*info = "Failed name lookup";
215 	}
216 
217 	*name = str;
218 
219 	return error;
220 }
221