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(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(buffer: name, buflen: *name - buf, str: "/" , namelen: 1); |
66 | if (!error && disconnected) |
67 | error = prepend(buffer: name, buflen: *name - buf, str: 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(ptr: res)) { |
102 | *name = buf; |
103 | return PTR_ERR(ptr: 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(buffer: name, buflen: *name - buf, str: "/proc" , namelen: 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: &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(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(ptr: res)) { |
134 | if (PTR_ERR(ptr: 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(ptr: res)) { |
142 | error = PTR_ERR(ptr: res); |
143 | *name = buf; |
144 | goto out; |
145 | } |
146 | } else if (!our_mnt(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(dentry: path->dentry) && d_is_positive(dentry: 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(p: &buf[aa_g_path_max - 2], q: "/" ); |
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 | */ |
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, buf: buffer, name: &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 | |