1 |
/* |
2 |
* security/ccsecurity/realpath.c |
3 |
* |
4 |
* Copyright (C) 2005-2010 NTT DATA CORPORATION |
5 |
* |
6 |
* Version: 1.7.2-pre 2010/03/21 |
7 |
* |
8 |
* This file is applicable to both 2.4.30 and 2.6.11 and later. |
9 |
* See README.ccs for ChangeLog. |
10 |
* |
11 |
*/ |
12 |
|
13 |
#include <linux/string.h> |
14 |
#include <linux/mm.h> |
15 |
#include <linux/utime.h> |
16 |
#include <linux/file.h> |
17 |
#include <linux/smp_lock.h> |
18 |
#include <linux/module.h> |
19 |
#include <linux/slab.h> |
20 |
#include <asm/uaccess.h> |
21 |
#include <asm/atomic.h> |
22 |
#include <linux/version.h> |
23 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) |
24 |
#include <linux/namei.h> |
25 |
#include <linux/mount.h> |
26 |
static const int ccs_lookup_flags = LOOKUP_FOLLOW; |
27 |
#else |
28 |
static const int ccs_lookup_flags = LOOKUP_FOLLOW | LOOKUP_POSITIVE; |
29 |
#endif |
30 |
#include <net/sock.h> |
31 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 5, 0) |
32 |
#include <linux/kthread.h> |
33 |
#endif |
34 |
#include <linux/proc_fs.h> |
35 |
#include "internal.h" |
36 |
|
37 |
static int ccs_kern_path(const char *pathname, int flags, struct path *path) |
38 |
{ |
39 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 28) |
40 |
if (!pathname || kern_path(pathname, flags, path)) |
41 |
return -ENOENT; |
42 |
#else |
43 |
struct nameidata nd; |
44 |
if (!pathname || path_lookup(pathname, flags, &nd)) |
45 |
return -ENOENT; |
46 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
47 |
*path = nd.path; |
48 |
#else |
49 |
path->dentry = nd.dentry; |
50 |
path->mnt = nd.mnt; |
51 |
#endif |
52 |
#endif |
53 |
return 0; |
54 |
} |
55 |
|
56 |
/** |
57 |
* ccs_get_absolute_path - Get the path of a dentry but ignores chroot'ed root. |
58 |
* |
59 |
* @path: Pointer to "struct path". |
60 |
* @buffer: Pointer to buffer to return value in. |
61 |
* @buflen: Sizeof @buffer. |
62 |
* |
63 |
* Returns the buffer on success, an error code otherwise. |
64 |
* |
65 |
* Caller holds the dcache_lock and vfsmount_lock. |
66 |
* Based on __d_path() in fs/dcache.c |
67 |
* |
68 |
* If dentry is a directory, trailing '/' is appended. |
69 |
* /proc/pid is represented as /proc/self if pid is current. |
70 |
*/ |
71 |
static char *ccs_get_absolute_path(struct path *path, char * const buffer, |
72 |
const int buflen) |
73 |
{ |
74 |
char *pos = buffer + buflen - 1; |
75 |
struct dentry *dentry = path->dentry; |
76 |
struct vfsmount *vfsmnt = path->mnt; |
77 |
bool is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)); |
78 |
const char *name; |
79 |
int len; |
80 |
|
81 |
if (buflen < 256) |
82 |
goto out; |
83 |
|
84 |
*pos = '\0'; |
85 |
for (;;) { |
86 |
struct dentry *parent; |
87 |
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { |
88 |
if (vfsmnt->mnt_parent == vfsmnt) |
89 |
break; |
90 |
dentry = vfsmnt->mnt_mountpoint; |
91 |
vfsmnt = vfsmnt->mnt_parent; |
92 |
continue; |
93 |
} |
94 |
if (is_dir) { |
95 |
is_dir = false; |
96 |
*--pos = '/'; |
97 |
} |
98 |
parent = dentry->d_parent; |
99 |
name = dentry->d_name.name; |
100 |
len = dentry->d_name.len; |
101 |
if (IS_ROOT(parent) && *name > '0' && *name <= '9' && |
102 |
parent->d_sb && |
103 |
parent->d_sb->s_magic == PROC_SUPER_MAGIC) { |
104 |
char *ep; |
105 |
const pid_t pid = (pid_t) simple_strtoul(name, &ep, |
106 |
10); |
107 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
108 |
const pid_t tgid = task_tgid_nr_ns(current, |
109 |
dentry->d_sb-> |
110 |
s_fs_info); |
111 |
if (!*ep && pid == tgid && tgid) { |
112 |
name = "self"; |
113 |
len = 4; |
114 |
} |
115 |
#else |
116 |
if (!*ep && pid == ccsecurity_exports.sys_getpid()) { |
117 |
name = "self"; |
118 |
len = 4; |
119 |
} |
120 |
#endif |
121 |
} |
122 |
pos -= len; |
123 |
if (pos <= buffer) |
124 |
goto out; |
125 |
memmove(pos, name, len); |
126 |
*--pos = '/'; |
127 |
dentry = parent; |
128 |
} |
129 |
if (*pos == '/') |
130 |
pos++; |
131 |
len = dentry->d_name.len; |
132 |
pos -= len; |
133 |
if (pos < buffer) |
134 |
goto out; |
135 |
memmove(pos, dentry->d_name.name, len); |
136 |
return pos; |
137 |
out: |
138 |
return ERR_PTR(-ENOMEM); |
139 |
} |
140 |
|
141 |
#define SOCKFS_MAGIC 0x534F434B |
142 |
|
143 |
/** |
144 |
* ccs_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. |
145 |
* |
146 |
* @path: Pointer to "struct path". |
147 |
* |
148 |
* Returns the realpath of the given @path on success, NULL otherwise. |
149 |
* |
150 |
* This function uses kzalloc(), so caller must kfree() if this function |
151 |
* didn't return NULL. |
152 |
*/ |
153 |
char *ccs_realpath_from_path(struct path *path) |
154 |
{ |
155 |
char *buf = NULL; |
156 |
char *name = NULL; |
157 |
unsigned int buf_len = PAGE_SIZE / 2; |
158 |
struct dentry *dentry = path->dentry; |
159 |
if (!dentry) |
160 |
return NULL; |
161 |
while (1) { |
162 |
char *pos; |
163 |
buf_len <<= 1; |
164 |
kfree(buf); |
165 |
buf = kmalloc(buf_len, CCS_GFP_FLAGS); |
166 |
if (!buf) |
167 |
break; |
168 |
/* Get better name for socket. */ |
169 |
if (dentry->d_sb && dentry->d_sb->s_magic == SOCKFS_MAGIC) { |
170 |
struct inode *inode = dentry->d_inode; |
171 |
struct socket *sock = inode ? SOCKET_I(inode) : NULL; |
172 |
struct sock *sk = sock ? sock->sk : NULL; |
173 |
if (sk) { |
174 |
snprintf(buf, buf_len - 1, "socket:[family=%u:" |
175 |
"type=%u:protocol=%u]", sk->sk_family, |
176 |
sk->sk_type, sk->sk_protocol); |
177 |
} else { |
178 |
snprintf(buf, buf_len - 1, "socket:[unknown]"); |
179 |
} |
180 |
name = ccs_encode(buf); |
181 |
break; |
182 |
} |
183 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) |
184 |
/* For "socket:[\$]" and "pipe:[\$]". */ |
185 |
if (dentry->d_op && dentry->d_op->d_dname) { |
186 |
pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1); |
187 |
if (IS_ERR(pos)) |
188 |
continue; |
189 |
name = ccs_encode(pos); |
190 |
break; |
191 |
} |
192 |
#endif |
193 |
if (!path->mnt) |
194 |
break; |
195 |
path_get(path); |
196 |
ccs_realpath_lock(); |
197 |
pos = ccs_get_absolute_path(path, buf, buf_len - 1); |
198 |
ccs_realpath_unlock(); |
199 |
path_put(path); |
200 |
if (IS_ERR(pos)) |
201 |
continue; |
202 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 34) |
203 |
/* Prepend "/proc" prefix if using internal proc vfs mount. */ |
204 |
if (path->mnt->mnt_flags & MNT_INTERNAL && |
205 |
path->mnt->mnt_sb->s_magic == PROC_SUPER_MAGIC) { |
206 |
pos -= 5; |
207 |
if (pos >= buf) |
208 |
memmove(pos, "/proc", 5); |
209 |
else |
210 |
continue; |
211 |
} |
212 |
#elif LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 33) |
213 |
/* Prepend "/proc" prefix if using internal proc vfs mount. */ |
214 |
if (path->mnt->mnt_parent == path->mnt && |
215 |
path->mnt->mnt_sb->s_magic == PROC_SUPER_MAGIC) { |
216 |
pos -= 5; |
217 |
if (pos >= buf) |
218 |
memmove(pos, "/proc", 5); |
219 |
else |
220 |
continue; |
221 |
} |
222 |
#endif |
223 |
name = ccs_encode(pos); |
224 |
break; |
225 |
} |
226 |
kfree(buf); |
227 |
if (!name) |
228 |
ccs_warn_oom(__func__); |
229 |
return name; |
230 |
} |
231 |
|
232 |
/** |
233 |
* ccs_symlink_path - Get symlink's pathname. |
234 |
* |
235 |
* @pathname: The pathname to solve. |
236 |
* @name: Pointer to "struct ccs_path_info". |
237 |
* |
238 |
* Returns 0 on success, negative value otherwise. |
239 |
* |
240 |
* This function uses kzalloc(), so caller must kfree() if this function |
241 |
* didn't return NULL. |
242 |
*/ |
243 |
int ccs_symlink_path(const char *pathname, struct ccs_path_info *name) |
244 |
{ |
245 |
char *buf; |
246 |
struct path path; |
247 |
if (ccs_kern_path(pathname, ccs_lookup_flags ^ LOOKUP_FOLLOW, &path)) |
248 |
return -ENOENT; |
249 |
buf = ccs_realpath_from_path(&path); |
250 |
path_put(&path); |
251 |
if (buf) { |
252 |
name->name = buf; |
253 |
ccs_fill_path_info(name); |
254 |
return 0; |
255 |
} |
256 |
return -ENOMEM; |
257 |
} |
258 |
|
259 |
/** |
260 |
* ccs_encode: Encode binary string to ascii string. |
261 |
* |
262 |
* @str: String in binary format. |
263 |
* |
264 |
* Returns pointer to @str in ascii format on success, NULL otherwise. |
265 |
* |
266 |
* This function uses kzalloc(), so caller must kfree() if this function |
267 |
* didn't return NULL. |
268 |
*/ |
269 |
char *ccs_encode(const char *str) |
270 |
{ |
271 |
int len = 0; |
272 |
const char *p = str; |
273 |
char *cp; |
274 |
char *cp0; |
275 |
if (!p) |
276 |
return NULL; |
277 |
while (*p) { |
278 |
const unsigned char c = *p++; |
279 |
if (c == '\\') |
280 |
len += 2; |
281 |
else if (c > ' ' && c < 127) |
282 |
len++; |
283 |
else |
284 |
len += 4; |
285 |
} |
286 |
len++; |
287 |
/* Reserve space for appending "/". */ |
288 |
cp = kzalloc(len + 10, CCS_GFP_FLAGS); |
289 |
if (!cp) |
290 |
return NULL; |
291 |
cp0 = cp; |
292 |
p = str; |
293 |
while (*p) { |
294 |
const unsigned char c = *p++; |
295 |
if (c == '\\') { |
296 |
*cp++ = '\\'; |
297 |
*cp++ = '\\'; |
298 |
} else if (c > ' ' && c < 127) { |
299 |
*cp++ = c; |
300 |
} else { |
301 |
*cp++ = '\\'; |
302 |
*cp++ = (c >> 6) + '0'; |
303 |
*cp++ = ((c >> 3) & 7) + '0'; |
304 |
*cp++ = (c & 7) + '0'; |
305 |
} |
306 |
} |
307 |
return cp0; |
308 |
} |
309 |
|
310 |
/** |
311 |
* ccs_get_path - Get dentry/vfsmmount of a pathname. |
312 |
* |
313 |
* @pathname: The pathname to solve. |
314 |
* @path: Pointer to "struct path". |
315 |
* |
316 |
* Returns 0 on success, negative value otherwise. |
317 |
*/ |
318 |
int ccs_get_path(const char *pathname, struct path *path) |
319 |
{ |
320 |
return ccs_kern_path(pathname, ccs_lookup_flags, path); |
321 |
} |