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 */
prepend(char ** buffer,int buflen,const char * str,int namelen)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  */
disconnect(const struct path * path,char * buf,char ** name,int flags,const char * disconnected)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  */
d_namespace_path(const struct path * path,char * buf,char ** name,int flags,const char * disconnected)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 (!res || IS_ERR(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 	/*
168 	 * Append "/" to the pathname.  The root directory is a special
169 	 * case; it already ends in slash.
170 	 */
171 	if (!error && isdir && ((*name)[1] != '\0' || (*name)[0] != '/'))
172 		strcpy(&buf[aa_g_path_max - 2], "/");
173 
174 	return error;
175 }
176 
177 /**
178  * aa_path_name - get the pathname to a buffer ensure dir / is appended
179  * @path: path the file  (NOT NULL)
180  * @flags: flags controlling path name generation
181  * @buffer: buffer to put name in (NOT NULL)
182  * @name: Returns - the generated path name if !error (NOT NULL)
183  * @info: Returns - information on why the path lookup failed (MAYBE NULL)
184  * @disconnected: string to prepend to disconnected paths
185  *
186  * @name is a pointer to the beginning of the pathname (which usually differs
187  * from the beginning of the buffer), or NULL.  If there is an error @name
188  * may contain a partial or invalid name that can be used for audit purposes,
189  * but it can not be used for mediation.
190  *
191  * We need PATH_IS_DIR to indicate whether the file is a directory or not
192  * because the file may not yet exist, and so we cannot check the inode's
193  * file type.
194  *
195  * Returns: %0 else error code if could retrieve name
196  */
aa_path_name(const struct path * path,int flags,char * buffer,const char ** name,const char ** info,const char * disconnected)197 int aa_path_name(const struct path *path, int flags, char *buffer,
198 		 const char **name, const char **info, const char *disconnected)
199 {
200 	char *str = NULL;
201 	int error = d_namespace_path(path, buffer, &str, flags, disconnected);
202 
203 	if (info && error) {
204 		if (error == -ENOENT)
205 			*info = "Failed name lookup - deleted entry";
206 		else if (error == -EACCES)
207 			*info = "Failed name lookup - disconnected path";
208 		else if (error == -ENAMETOOLONG)
209 			*info = "Failed name lookup - name too long";
210 		else
211 			*info = "Failed name lookup";
212 	}
213 
214 	*name = str;
215 
216 	return error;
217 }
218