1 |
/* |
2 |
* security/ccsecurity/realpath.c |
3 |
* |
4 |
* Copyright (C) 2005-2009 NTT DATA CORPORATION |
5 |
* |
6 |
* Version: 1.7.0-pre 2009/08/08 |
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 0 on success, -ENOMEM 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 |
* Characters out of 0x20 < c < 0x7F range are converted to |
70 |
* \ooo style octal string. |
71 |
* Character \ is converted to \\ string. |
72 |
*/ |
73 |
static int ccs_get_absolute_path(struct path *path, char *buffer, int buflen) |
74 |
{ |
75 |
/***** CRITICAL SECTION START *****/ |
76 |
char *start = buffer; |
77 |
char *end = buffer + buflen; |
78 |
struct dentry *dentry = path->dentry; |
79 |
struct vfsmount *vfsmnt = path->mnt; |
80 |
bool is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)); |
81 |
|
82 |
if (buflen < 256) |
83 |
goto out; |
84 |
|
85 |
*--end = '\0'; |
86 |
buflen--; |
87 |
|
88 |
for (;;) { |
89 |
struct dentry *parent; |
90 |
|
91 |
if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { |
92 |
/* Global root? */ |
93 |
if (vfsmnt->mnt_parent == vfsmnt) |
94 |
break; |
95 |
dentry = vfsmnt->mnt_mountpoint; |
96 |
vfsmnt = vfsmnt->mnt_parent; |
97 |
continue; |
98 |
} |
99 |
if (is_dir) { |
100 |
is_dir = false; |
101 |
*--end = '/'; |
102 |
buflen--; |
103 |
} |
104 |
parent = dentry->d_parent; |
105 |
{ |
106 |
const char *sp = dentry->d_name.name; |
107 |
const char *cp = sp + dentry->d_name.len - 1; |
108 |
unsigned char c; |
109 |
|
110 |
/* |
111 |
* Exception: Use /proc/self/ rather than |
112 |
* /proc/\$/ for current process. |
113 |
*/ |
114 |
if (IS_ROOT(parent) && *sp > '0' && *sp <= '9' && |
115 |
parent->d_sb && |
116 |
parent->d_sb->s_magic == PROC_SUPER_MAGIC) { |
117 |
char *ep; |
118 |
const pid_t pid |
119 |
= (pid_t) simple_strtoul(sp, &ep, 10); |
120 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
121 |
const pid_t tgid |
122 |
= task_tgid_nr_ns(current, |
123 |
dentry->d_sb-> |
124 |
s_fs_info); |
125 |
if (!*ep && pid == tgid && tgid) { |
126 |
sp = "self"; |
127 |
cp = sp + 3; |
128 |
} |
129 |
#else |
130 |
if (!*ep && pid == sys_getpid()) { |
131 |
sp = "self"; |
132 |
cp = sp + 3; |
133 |
} |
134 |
#endif |
135 |
} |
136 |
|
137 |
while (sp <= cp) { |
138 |
c = *(unsigned char *) cp; |
139 |
if (c == '\\') { |
140 |
buflen -= 2; |
141 |
if (buflen < 0) |
142 |
goto out; |
143 |
*--end = '\\'; |
144 |
*--end = '\\'; |
145 |
} else if (c > ' ' && c < 127) { |
146 |
if (--buflen < 0) |
147 |
goto out; |
148 |
*--end = (char) c; |
149 |
} else { |
150 |
buflen -= 4; |
151 |
if (buflen < 0) |
152 |
goto out; |
153 |
*--end = (c & 7) + '0'; |
154 |
*--end = ((c >> 3) & 7) + '0'; |
155 |
*--end = (c >> 6) + '0'; |
156 |
*--end = '\\'; |
157 |
} |
158 |
cp--; |
159 |
} |
160 |
if (--buflen < 0) |
161 |
goto out; |
162 |
*--end = '/'; |
163 |
} |
164 |
dentry = parent; |
165 |
} |
166 |
if (*end == '/') { |
167 |
buflen++; |
168 |
end++; |
169 |
} |
170 |
{ |
171 |
const char *sp = dentry->d_name.name; |
172 |
const char *cp = sp + dentry->d_name.len - 1; |
173 |
unsigned char c; |
174 |
while (sp <= cp) { |
175 |
c = *(unsigned char *) cp; |
176 |
if (c == '\\') { |
177 |
buflen -= 2; |
178 |
if (buflen < 0) |
179 |
goto out; |
180 |
*--end = '\\'; |
181 |
*--end = '\\'; |
182 |
} else if (c > ' ' && c < 127) { |
183 |
if (--buflen < 0) |
184 |
goto out; |
185 |
*--end = (char) c; |
186 |
} else { |
187 |
buflen -= 4; |
188 |
if (buflen < 0) |
189 |
goto out; |
190 |
*--end = (c & 7) + '0'; |
191 |
*--end = ((c >> 3) & 7) + '0'; |
192 |
*--end = (c >> 6) + '0'; |
193 |
*--end = '\\'; |
194 |
} |
195 |
cp--; |
196 |
} |
197 |
} |
198 |
/* Move the pathname to the top of the buffer. */ |
199 |
memmove(start, end, strlen(end) + 1); |
200 |
return 0; |
201 |
out: |
202 |
return -ENOMEM; |
203 |
/***** CRITICAL SECTION END *****/ |
204 |
} |
205 |
|
206 |
#define SOCKFS_MAGIC 0x534F434B |
207 |
|
208 |
/** |
209 |
* ccs_realpath_from_path2 - Returns realpath(3) of the given dentry but ignores chroot'ed root. |
210 |
* |
211 |
* @path: Pointer to "struct path". |
212 |
* @newname: Pointer to buffer to return value in. |
213 |
* @newname_len: Size of @newname. |
214 |
* |
215 |
* Returns 0 on success, negative value otherwise. |
216 |
*/ |
217 |
static int ccs_realpath_from_path2(struct path *path, char *newname, |
218 |
int newname_len) |
219 |
{ |
220 |
int error = -EINVAL; |
221 |
struct dentry *dentry = path->dentry; |
222 |
if (!dentry || !newname || newname_len <= 2048) |
223 |
goto out; |
224 |
/* Get better name for socket. */ |
225 |
if (dentry->d_sb && dentry->d_sb->s_magic == SOCKFS_MAGIC) { |
226 |
struct inode *inode = dentry->d_inode; |
227 |
struct socket *sock = inode ? SOCKET_I(inode) : NULL; |
228 |
struct sock *sk = sock ? sock->sk : NULL; |
229 |
if (sk) { |
230 |
snprintf(newname, newname_len - 1, |
231 |
"socket:[family=%u:type=%u:protocol=%u]", |
232 |
sk->sk_family, sk->sk_type, sk->sk_protocol); |
233 |
} else { |
234 |
snprintf(newname, newname_len - 1, "socket:[unknown]"); |
235 |
} |
236 |
return 0; |
237 |
} |
238 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) |
239 |
if (dentry->d_op && dentry->d_op->d_dname) { |
240 |
/* For "socket:[\$]" and "pipe:[\$]". */ |
241 |
static const int offset = 1536; |
242 |
char *dp = newname; |
243 |
char *sp = dentry->d_op->d_dname(dentry, newname + offset, |
244 |
newname_len - offset); |
245 |
if (IS_ERR(sp)) { |
246 |
error = PTR_ERR(sp); |
247 |
goto out; |
248 |
} |
249 |
error = -ENOMEM; |
250 |
newname += offset; |
251 |
while (1) { |
252 |
const unsigned char c = *(unsigned char *) sp++; |
253 |
if (c == '\\') { |
254 |
if (dp + 2 >= newname) |
255 |
break; |
256 |
*dp++ = '\\'; |
257 |
*dp++ = '\\'; |
258 |
} else if (c > ' ' && c < 127) { |
259 |
if (dp + 1 >= newname) |
260 |
break; |
261 |
*dp++ = (char) c; |
262 |
} else if (c) { |
263 |
if (dp + 4 >= newname) |
264 |
break; |
265 |
*dp++ = '\\'; |
266 |
*dp++ = (c >> 6) + '0'; |
267 |
*dp++ = ((c >> 3) & 7) + '0'; |
268 |
*dp++ = (c & 7) + '0'; |
269 |
} else { |
270 |
*dp = '\0'; |
271 |
return 0; |
272 |
} |
273 |
} |
274 |
goto out; |
275 |
} |
276 |
#endif |
277 |
if (!path->mnt) |
278 |
goto out; |
279 |
path_get(path); |
280 |
/***** CRITICAL SECTION START *****/ |
281 |
ccs_realpath_lock(); |
282 |
error = ccs_get_absolute_path(path, newname, newname_len); |
283 |
ccs_realpath_unlock(); |
284 |
/***** CRITICAL SECTION END *****/ |
285 |
path_put(path); |
286 |
out: |
287 |
if (error) |
288 |
printk(KERN_WARNING "ccs_realpath: Pathname too long. (%d)\n", |
289 |
error); |
290 |
return error; |
291 |
} |
292 |
|
293 |
/** |
294 |
* ccs_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. |
295 |
* |
296 |
* @path: Pointer to "struct path". |
297 |
* |
298 |
* Returns the realpath of the given @path on success, NULL otherwise. |
299 |
* |
300 |
* These functions use kzalloc(), so caller must kfree() |
301 |
* if these functions didn't return NULL. |
302 |
*/ |
303 |
char *ccs_realpath_from_path(struct path *path) |
304 |
{ |
305 |
char *buf = kzalloc(CCS_MAX_PATHNAME_LEN, GFP_KERNEL); |
306 |
if (buf && |
307 |
ccs_realpath_from_path2(path, buf, CCS_MAX_PATHNAME_LEN - 2) == 0) |
308 |
return buf; |
309 |
kfree(buf); |
310 |
return NULL; |
311 |
} |
312 |
|
313 |
/** |
314 |
* ccs_symlink_path - Get symlink's pathname. |
315 |
* |
316 |
* @pathname: The pathname to solve. |
317 |
* @ee: Pointer to "struct ccs_execve_entry". |
318 |
* |
319 |
* Returns 0 on success, negative value otherwise. |
320 |
*/ |
321 |
int ccs_symlink_path(const char *pathname, struct ccs_execve_entry *ee) |
322 |
{ |
323 |
struct path path; |
324 |
int ret; |
325 |
if (ccs_kern_path(pathname, ccs_lookup_flags ^ LOOKUP_FOLLOW, &path)) |
326 |
return -ENOENT; |
327 |
ret = ccs_realpath_from_path2(&path, ee->program_path, |
328 |
CCS_MAX_PATHNAME_LEN - 1); |
329 |
path_put(&path); |
330 |
return ret; |
331 |
} |
332 |
|
333 |
/** |
334 |
* ccs_encode: Encode binary string to ascii string. |
335 |
* |
336 |
* @str: String in binary format. |
337 |
* |
338 |
* Returns pointer to @str in ascii format on success, NULL otherwise. |
339 |
* |
340 |
* This function uses kzalloc(), so caller must kfree() if this function |
341 |
* didn't return NULL. |
342 |
*/ |
343 |
char *ccs_encode(const char *str) |
344 |
{ |
345 |
int len = 0; |
346 |
const char *p = str; |
347 |
char *cp; |
348 |
char *cp0; |
349 |
if (!p) |
350 |
return NULL; |
351 |
while (*p) { |
352 |
const unsigned char c = *p++; |
353 |
if (c == '\\') |
354 |
len += 2; |
355 |
else if (c > ' ' && c < 127) |
356 |
len++; |
357 |
else |
358 |
len += 4; |
359 |
} |
360 |
len++; |
361 |
cp = kzalloc(len, GFP_KERNEL); |
362 |
if (!cp) |
363 |
return NULL; |
364 |
cp0 = cp; |
365 |
p = str; |
366 |
while (*p) { |
367 |
const unsigned char c = *p++; |
368 |
if (c == '\\') { |
369 |
*cp++ = '\\'; |
370 |
*cp++ = '\\'; |
371 |
} else if (c > ' ' && c < 127) { |
372 |
*cp++ = c; |
373 |
} else { |
374 |
*cp++ = '\\'; |
375 |
*cp++ = (c >> 6) + '0'; |
376 |
*cp++ = ((c >> 3) & 7) + '0'; |
377 |
*cp++ = (c & 7) + '0'; |
378 |
} |
379 |
} |
380 |
return cp0; |
381 |
} |
382 |
|
383 |
/** |
384 |
* ccs_get_path - Get dentry/vfsmmount of a pathname. |
385 |
* |
386 |
* @pathname: The pathname to solve. |
387 |
* @path: Pointer to "struct path". |
388 |
* |
389 |
* Returns 0 on success, negative value otherwise. |
390 |
*/ |
391 |
int ccs_get_path(const char *pathname, struct path *path) |
392 |
{ |
393 |
return ccs_kern_path(pathname, ccs_lookup_flags, path); |
394 |
} |