1 |
/* |
2 |
* security/ccsecurity/realpath.c |
3 |
* |
4 |
* Copyright (C) 2005-2010 NTT DATA CORPORATION |
5 |
* |
6 |
* Version: 1.8.0-pre 2010/08/01 |
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 |
*/ |
70 |
static char *ccs_get_absolute_path(struct path *path, char * const buffer, |
71 |
const int buflen) |
72 |
{ |
73 |
char *pos = buffer + buflen - 1; |
74 |
struct dentry *dentry = path->dentry; |
75 |
struct vfsmount *vfsmnt = path->mnt; |
76 |
const char *name; |
77 |
int len; |
78 |
|
79 |
if (buflen < 256) |
80 |
goto out; |
81 |
|
82 |
*pos = '\0'; |
83 |
if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) |
84 |
*--pos = '/'; |
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 |
parent = dentry->d_parent; |
95 |
name = dentry->d_name.name; |
96 |
len = dentry->d_name.len; |
97 |
pos -= len; |
98 |
if (pos <= buffer) |
99 |
goto out; |
100 |
memmove(pos, name, len); |
101 |
*--pos = '/'; |
102 |
dentry = parent; |
103 |
} |
104 |
if (*pos == '/') |
105 |
pos++; |
106 |
len = dentry->d_name.len; |
107 |
pos -= len; |
108 |
if (pos < buffer) |
109 |
goto out; |
110 |
memmove(pos, dentry->d_name.name, len); |
111 |
return pos; |
112 |
out: |
113 |
return ERR_PTR(-ENOMEM); |
114 |
} |
115 |
|
116 |
/** |
117 |
* ccs_get_dentry_path - Get the path of a dentry but ignores chroot'ed root. |
118 |
* |
119 |
* @dentry: Pointer to "struct dentry". |
120 |
* @buffer: Pointer to buffer to return value in. |
121 |
* @buflen: Sizeof @buffer. |
122 |
* |
123 |
* Returns the buffer on success, an error code otherwise. |
124 |
* |
125 |
* Caller holds the dcache_lock. |
126 |
* Based on dentry_path() in fs/dcache.c |
127 |
* |
128 |
* If dentry is a directory, trailing '/' is appended. |
129 |
*/ |
130 |
static char *ccs_get_dentry_path(struct dentry *dentry, char * const buffer, |
131 |
const int buflen) |
132 |
{ |
133 |
char *pos = buffer + buflen - 1; |
134 |
if (buflen < 256) |
135 |
goto out; |
136 |
*pos = '\0'; |
137 |
if (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)) |
138 |
*--pos = '/'; |
139 |
while (!IS_ROOT(dentry)) { |
140 |
struct dentry *parent = dentry->d_parent; |
141 |
const char *name = dentry->d_name.name; |
142 |
const int len = dentry->d_name.len; |
143 |
pos -= len; |
144 |
if (pos <= buffer) |
145 |
goto out; |
146 |
memmove(pos, name, len); |
147 |
*--pos = '/'; |
148 |
dentry = parent; |
149 |
} |
150 |
return pos; |
151 |
out: |
152 |
return ERR_PTR(-ENOMEM); |
153 |
} |
154 |
|
155 |
#define SOCKFS_MAGIC 0x534F434B |
156 |
|
157 |
/** |
158 |
* ccs_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. |
159 |
* |
160 |
* @path: Pointer to "struct path". |
161 |
* |
162 |
* Returns the realpath of the given @path on success, NULL otherwise. |
163 |
* |
164 |
* This function uses kzalloc(), so caller must kfree() if this function |
165 |
* didn't return NULL. |
166 |
*/ |
167 |
char *ccs_realpath_from_path(struct path *path) |
168 |
{ |
169 |
char *buf = NULL; |
170 |
char *name = NULL; |
171 |
unsigned int buf_len = PAGE_SIZE / 2; |
172 |
struct dentry *dentry = path->dentry; |
173 |
struct super_block *sb; |
174 |
if (!dentry) |
175 |
return NULL; |
176 |
sb = dentry->d_sb; |
177 |
while (1) { |
178 |
char *pos; |
179 |
buf_len <<= 1; |
180 |
kfree(buf); |
181 |
buf = kmalloc(buf_len, CCS_GFP_FLAGS); |
182 |
if (!buf) |
183 |
break; |
184 |
/* Get better name for socket. */ |
185 |
if (sb->s_magic == SOCKFS_MAGIC) { |
186 |
struct inode *inode = dentry->d_inode; |
187 |
struct socket *sock = inode ? SOCKET_I(inode) : NULL; |
188 |
struct sock *sk = sock ? sock->sk : NULL; |
189 |
if (sk) { |
190 |
snprintf(buf, buf_len - 1, "socket:[family=%u:" |
191 |
"type=%u:protocol=%u]", sk->sk_family, |
192 |
sk->sk_type, sk->sk_protocol); |
193 |
} else { |
194 |
snprintf(buf, buf_len - 1, "socket:[unknown]"); |
195 |
} |
196 |
name = ccs_encode(buf); |
197 |
break; |
198 |
} |
199 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 22) |
200 |
/* For "socket:[\$]" and "pipe:[\$]". */ |
201 |
if (dentry->d_op && dentry->d_op->d_dname) { |
202 |
pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1); |
203 |
if (IS_ERR(pos)) |
204 |
continue; |
205 |
name = ccs_encode(pos); |
206 |
break; |
207 |
} |
208 |
#endif |
209 |
/* |
210 |
* Get local name for filesystems without rename() operation. |
211 |
*/ |
212 |
if (sb->s_root->d_inode && sb->s_root->d_inode->i_op && |
213 |
!sb->s_root->d_inode->i_op->rename) { |
214 |
spin_lock(&dcache_lock); |
215 |
pos = ccs_get_dentry_path(dentry, buf, buf_len - 1); |
216 |
spin_unlock(&dcache_lock); |
217 |
if (IS_ERR(pos)) |
218 |
continue; |
219 |
/* |
220 |
* Convert from $PID to self if $PID is current thread. |
221 |
*/ |
222 |
if (sb->s_magic == PROC_SUPER_MAGIC && *pos == '/') { |
223 |
char *ep; |
224 |
const pid_t pid = (pid_t) |
225 |
simple_strtoul(pos + 1, &ep, 10); |
226 |
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 25) |
227 |
if (*ep == '/' && pid && pid == |
228 |
task_tgid_nr_ns(current, sb->s_fs_info)) { |
229 |
pos = ep - 5; |
230 |
if (pos < buf) |
231 |
continue; |
232 |
memmove(pos, "/self", 5); |
233 |
} |
234 |
#else |
235 |
if (*ep == '/' && |
236 |
pid == ccsecurity_exports.sys_getpid()) { |
237 |
pos = ep - 5; |
238 |
if (pos < buf) |
239 |
continue; |
240 |
memmove(pos, "/self", 5); |
241 |
} |
242 |
#endif |
243 |
} |
244 |
/* Prepend filesystem name. */ |
245 |
{ |
246 |
const char *name = sb->s_type->name; |
247 |
const int name_len = strlen(name); |
248 |
pos -= name_len + 1; |
249 |
if (pos < buf) |
250 |
continue; |
251 |
memmove(pos, name, name_len); |
252 |
pos[name_len] = ':'; |
253 |
} |
254 |
name = ccs_encode(pos); |
255 |
break; |
256 |
} |
257 |
if (!path->mnt) |
258 |
break; |
259 |
path_get(path); |
260 |
ccs_realpath_lock(); |
261 |
pos = ccs_get_absolute_path(path, buf, buf_len - 1); |
262 |
ccs_realpath_unlock(); |
263 |
path_put(path); |
264 |
if (IS_ERR(pos)) |
265 |
continue; |
266 |
name = ccs_encode(pos); |
267 |
break; |
268 |
} |
269 |
kfree(buf); |
270 |
if (!name) |
271 |
ccs_warn_oom(__func__); |
272 |
return name; |
273 |
} |
274 |
|
275 |
/** |
276 |
* ccs_symlink_path - Get symlink's pathname. |
277 |
* |
278 |
* @pathname: The pathname to solve. |
279 |
* @name: Pointer to "struct ccs_path_info". |
280 |
* |
281 |
* Returns 0 on success, negative value otherwise. |
282 |
* |
283 |
* This function uses kzalloc(), so caller must kfree() if this function |
284 |
* didn't return NULL. |
285 |
*/ |
286 |
int ccs_symlink_path(const char *pathname, struct ccs_path_info *name) |
287 |
{ |
288 |
char *buf; |
289 |
struct path path; |
290 |
if (ccs_kern_path(pathname, ccs_lookup_flags ^ LOOKUP_FOLLOW, &path)) |
291 |
return -ENOENT; |
292 |
buf = ccs_realpath_from_path(&path); |
293 |
path_put(&path); |
294 |
if (buf) { |
295 |
name->name = buf; |
296 |
ccs_fill_path_info(name); |
297 |
return 0; |
298 |
} |
299 |
return -ENOMEM; |
300 |
} |
301 |
|
302 |
/** |
303 |
* ccs_encode: Encode binary string to ascii string. |
304 |
* |
305 |
* @str: String in binary format. |
306 |
* |
307 |
* Returns pointer to @str in ascii format on success, NULL otherwise. |
308 |
* |
309 |
* This function uses kzalloc(), so caller must kfree() if this function |
310 |
* didn't return NULL. |
311 |
*/ |
312 |
char *ccs_encode(const char *str) |
313 |
{ |
314 |
int len = 0; |
315 |
const char *p = str; |
316 |
char *cp; |
317 |
char *cp0; |
318 |
if (!p) |
319 |
return NULL; |
320 |
while (*p) { |
321 |
const unsigned char c = *p++; |
322 |
if (c == '\\') |
323 |
len += 2; |
324 |
else if (c > ' ' && c < 127) |
325 |
len++; |
326 |
else |
327 |
len += 4; |
328 |
} |
329 |
len++; |
330 |
/* Reserve space for appending "/". */ |
331 |
cp = kzalloc(len + 10, CCS_GFP_FLAGS); |
332 |
if (!cp) |
333 |
return NULL; |
334 |
cp0 = cp; |
335 |
p = str; |
336 |
while (*p) { |
337 |
const unsigned char c = *p++; |
338 |
if (c == '\\') { |
339 |
*cp++ = '\\'; |
340 |
*cp++ = '\\'; |
341 |
} else if (c > ' ' && c < 127) { |
342 |
*cp++ = c; |
343 |
} else { |
344 |
*cp++ = '\\'; |
345 |
*cp++ = (c >> 6) + '0'; |
346 |
*cp++ = ((c >> 3) & 7) + '0'; |
347 |
*cp++ = (c & 7) + '0'; |
348 |
} |
349 |
} |
350 |
return cp0; |
351 |
} |
352 |
|
353 |
/** |
354 |
* ccs_get_path - Get dentry/vfsmmount of a pathname. |
355 |
* |
356 |
* @pathname: The pathname to solve. |
357 |
* @path: Pointer to "struct path". |
358 |
* |
359 |
* Returns 0 on success, negative value otherwise. |
360 |
*/ |
361 |
int ccs_get_path(const char *pathname, struct path *path) |
362 |
{ |
363 |
return ccs_kern_path(pathname, ccs_lookup_flags, path); |
364 |
} |