Develop and Download Open Source Software

Browse Subversion Repository

Contents of /trunk/caitsith-patch/security/caitsith/realpath.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 386 - (show annotations) (download) (as text)
Mon Nov 13 11:36:37 2023 UTC (4 months, 4 weeks ago) by kumaneko
File MIME type: text/x-csrc
File size: 16949 byte(s)


1 /*
2 * security/caitsith/realpath.c
3 *
4 * Copyright (C) 2005-2012 NTT DATA CORPORATION
5 *
6 * Version: 0.2.11 2023/05/27
7 */
8
9 #include "internal.h"
10
11 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36) && LINUX_VERSION_CODE < KERNEL_VERSION(3, 2, 0)
12 #include <linux/nsproxy.h>
13 #include <linux/mnt_namespace.h>
14 #endif
15 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
16 #include <linux/proc_fs.h>
17 #endif
18
19 /***** SECTION1: Constants definition *****/
20
21 /***** SECTION2: Structure definition *****/
22
23 /***** SECTION3: Prototype definition section *****/
24
25 static char *cs_get_absolute_path(const struct path *path, char * const buffer,
26 const int buflen);
27 static char *cs_get_dentry_path(struct dentry *dentry, char * const buffer,
28 const int buflen);
29 static char *cs_get_local_path(struct dentry *dentry, char * const buffer,
30 const int buflen);
31 static int cs_const_part_length(const char *filename);
32
33 /***** SECTION4: Standalone functions section *****/
34
35 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 37)
36
37 /**
38 * cs_realpath_lock - Take locks for __d_path().
39 *
40 * Returns nothing.
41 */
42 static inline void cs_realpath_lock(void)
43 {
44 /* dcache_lock is locked by __d_path(). */
45 /* vfsmount_lock is locked by __d_path(). */
46 }
47
48 /**
49 * cs_realpath_unlock - Release locks for __d_path().
50 *
51 * Returns nothing.
52 */
53 static inline void cs_realpath_unlock(void)
54 {
55 /* vfsmount_lock is unlocked by __d_path(). */
56 /* dcache_lock is unlocked by __d_path(). */
57 }
58
59 #elif LINUX_VERSION_CODE == KERNEL_VERSION(2, 6, 36)
60
61 /**
62 * cs_realpath_lock - Take locks for __d_path().
63 *
64 * Returns nothing.
65 */
66 static inline void cs_realpath_lock(void)
67 {
68 spin_lock(&dcache_lock);
69 /* vfsmount_lock is locked by __d_path(). */
70 }
71
72 /**
73 * cs_realpath_unlock - Release locks for __d_path().
74 *
75 * Returns nothing.
76 */
77 static inline void cs_realpath_unlock(void)
78 {
79 /* vfsmount_lock is unlocked by __d_path(). */
80 spin_unlock(&dcache_lock);
81 }
82
83 #elif defined(D_PATH_DISCONNECT) && !defined(CONFIG_SUSE_KERNEL)
84
85 /**
86 * cs_realpath_lock - Take locks for __d_path().
87 *
88 * Returns nothing.
89 *
90 * Original unambiguous-__d_path.diff in patches.apparmor.tar.bz2 inversed the
91 * order of holding dcache_lock and vfsmount_lock. That patch was applied on
92 * (at least) SUSE 11.1 and Ubuntu 8.10 and Ubuntu 9.04 kernels.
93 *
94 * However, that patch was updated to use original order and the updated patch
95 * is applied to (as far as I know) only SUSE kernels.
96 *
97 * Therefore, I need to use original order for SUSE 11.1 kernels and inversed
98 * order for other kernels. I detect it by checking D_PATH_DISCONNECT and
99 * CONFIG_SUSE_KERNEL. I don't know whether other distributions are using the
100 * updated patch or not. If you got deadlock, check fs/dcache.c for locking
101 * order, and add " && 0" to this "#elif " block if fs/dcache.c uses original
102 * order.
103 */
104 static inline void cs_realpath_lock(void)
105 {
106 spin_lock(caitsith_exports.vfsmount_lock);
107 spin_lock(&dcache_lock);
108 }
109
110 /**
111 * cs_realpath_unlock - Release locks for __d_path().
112 *
113 * Returns nothing.
114 */
115 static inline void cs_realpath_unlock(void)
116 {
117 spin_unlock(&dcache_lock);
118 spin_unlock(caitsith_exports.vfsmount_lock);
119 }
120
121 #else
122
123 /**
124 * cs_realpath_lock - Take locks for __d_path().
125 *
126 * Returns nothing.
127 */
128 static inline void cs_realpath_lock(void)
129 {
130 spin_lock(&dcache_lock);
131 spin_lock(caitsith_exports.vfsmount_lock);
132 }
133
134 /**
135 * cs_realpath_unlock - Release locks for __d_path().
136 *
137 * Returns nothing.
138 */
139 static inline void cs_realpath_unlock(void)
140 {
141 spin_unlock(caitsith_exports.vfsmount_lock);
142 spin_unlock(&dcache_lock);
143 }
144
145 #endif
146
147 /***** SECTION5: Variables definition section *****/
148
149 /***** SECTION6: Dependent functions section *****/
150
151 /**
152 * cs_get_absolute_path - Get the path of a dentry but ignores chroot'ed root.
153 *
154 * @path: Pointer to "struct path".
155 * @buffer: Pointer to buffer to return value in.
156 * @buflen: Sizeof @buffer.
157 *
158 * Returns the buffer on success, an error code otherwise.
159 *
160 * Caller holds the dcache_lock and vfsmount_lock.
161 * Based on __d_path() in fs/dcache.c
162 */
163 static char *cs_get_absolute_path(const struct path *path, char * const buffer,
164 const int buflen)
165 {
166 #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 2, 0)
167 if (buflen < 256)
168 return ERR_PTR(-ENOMEM);
169 return caitsith_exports.d_absolute_path(path, buffer, buflen - 1);
170 #elif LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 36)
171 /*
172 * __d_path() will start returning NULL by backporting commit 02125a82
173 * "fix apparmor dereferencing potentially freed dentry, sanitize
174 * __d_path() API".
175 *
176 * Unfortunately, __d_path() after applying that commit always returns
177 * NULL when root is empty. d_absolute_path() is provided for TOMOYO
178 * 2.x and AppArmor but CaitSith does not use it, for CaitSith
179 * might be built as a loadable kernel module and there is no warrantee
180 * that CaitSith is recompiled after applying that commit. Also,
181 * I don't want to search /proc/kallsyms for d_absolute_path() because
182 * I want to keep CaitSith architecture independent. Thus, supply
183 * non empty root like AppArmor's d_namespace_path() did.
184 */
185 static bool cs_no_empty;
186 char *pos;
187
188 if (buflen < 256)
189 return ERR_PTR(-ENOMEM);
190 if (!cs_no_empty) {
191 struct path root = { };
192
193 pos = caitsith_exports.__d_path(path, &root, buffer,
194 buflen - 1);
195 } else {
196 pos = NULL;
197 }
198 if (!pos) {
199 struct task_struct *task = current;
200 struct path root;
201 struct path tmp;
202
203 spin_lock(&task->fs->lock);
204 root.mnt = task->nsproxy->mnt_ns->root;
205 root.dentry = root.mnt->mnt_root;
206 path_get(&root);
207 spin_unlock(&task->fs->lock);
208 tmp = root;
209 pos = caitsith_exports.__d_path(path, &tmp, buffer,
210 buflen - 1);
211 path_put(&root);
212 if (pos)
213 return pos;
214 /* Remember if __d_path() needs non empty root. */
215 cs_no_empty = true;
216 pos = ERR_PTR(-EINVAL);
217 }
218 return pos;
219 #else
220 char *pos = buffer + buflen - 1;
221 struct dentry *dentry = path->dentry;
222 struct vfsmount *vfsmnt = path->mnt;
223 const char *name;
224 int len;
225
226 if (buflen < 256)
227 goto out;
228
229 *pos = '\0';
230 for (;;) {
231 struct dentry *parent;
232
233 if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) {
234 if (vfsmnt->mnt_parent == vfsmnt)
235 break;
236 dentry = vfsmnt->mnt_mountpoint;
237 vfsmnt = vfsmnt->mnt_parent;
238 continue;
239 }
240 parent = dentry->d_parent;
241 name = dentry->d_name.name;
242 len = dentry->d_name.len;
243 pos -= len;
244 if (pos <= buffer)
245 goto out;
246 memmove(pos, name, len);
247 *--pos = '/';
248 dentry = parent;
249 }
250 if (*pos == '/')
251 pos++;
252 len = dentry->d_name.len;
253 pos -= len;
254 if (pos < buffer)
255 goto out;
256 memmove(pos, dentry->d_name.name, len);
257 return pos;
258 out:
259 return ERR_PTR(-ENOMEM);
260 #endif
261 }
262
263 /**
264 * cs_get_dentry_path - Get the path of a dentry.
265 *
266 * @dentry: Pointer to "struct dentry".
267 * @buffer: Pointer to buffer to return value in.
268 * @buflen: Sizeof @buffer.
269 *
270 * Returns the buffer on success, an error code otherwise.
271 *
272 * Based on dentry_path() in fs/dcache.c
273 */
274 static char *cs_get_dentry_path(struct dentry *dentry, char * const buffer,
275 const int buflen)
276 {
277 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 38)
278 if (buflen < 256)
279 return ERR_PTR(-ENOMEM);
280 /* rename_lock is locked/unlocked by dentry_path_raw(). */
281 return dentry_path_raw(dentry, buffer, buflen - 1);
282 #else
283 char *pos = buffer + buflen - 1;
284
285 if (buflen < 256)
286 return ERR_PTR(-ENOMEM);
287 *pos = '\0';
288 spin_lock(&dcache_lock);
289 while (!IS_ROOT(dentry)) {
290 struct dentry *parent = dentry->d_parent;
291 const char *name = dentry->d_name.name;
292 const int len = dentry->d_name.len;
293
294 pos -= len;
295 if (pos <= buffer) {
296 pos = ERR_PTR(-ENOMEM);
297 break;
298 }
299 memmove(pos, name, len);
300 *--pos = '/';
301 dentry = parent;
302 }
303 spin_unlock(&dcache_lock);
304 if (pos == buffer + buflen - 1)
305 *--pos = '/';
306 return pos;
307 #endif
308 }
309
310 /**
311 * cs_get_local_path - Get the path of a dentry.
312 *
313 * @dentry: Pointer to "struct dentry".
314 * @buffer: Pointer to buffer to return value in.
315 * @buflen: Sizeof @buffer.
316 *
317 * Returns the buffer on success, an error code otherwise.
318 */
319 static char *cs_get_local_path(struct dentry *dentry, char * const buffer,
320 const int buflen)
321 {
322 struct super_block *sb = dentry->d_sb;
323 char *pos = cs_get_dentry_path(dentry, buffer, buflen);
324
325 if (IS_ERR(pos))
326 return pos;
327 /* Convert from $PID to self if $PID is current thread. */
328 if (sb->s_magic == PROC_SUPER_MAGIC && *pos == '/') {
329 char *ep;
330 const pid_t pid = (pid_t) simple_strtoul(pos + 1, &ep, 10);
331
332 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0)
333 if (*ep == '/' && pid && pid ==
334 task_tgid_nr_ns(current, proc_pid_ns(sb))) {
335 pos = ep - 5;
336 if (pos < buffer)
337 goto out;
338 memmove(pos, "/self", 5);
339 }
340 #else
341 if (*ep == '/' && pid && pid ==
342 task_tgid_nr_ns(current, sb->s_fs_info)) {
343 pos = ep - 5;
344 if (pos < buffer)
345 goto out;
346 memmove(pos, "/self", 5);
347 }
348 #endif
349 goto prepend_filesystem_name;
350 }
351 /* Use filesystem name for unnamed devices. */
352 if (!MAJOR(sb->s_dev))
353 goto prepend_filesystem_name;
354 {
355 struct inode *inode = d_backing_inode(sb->s_root);
356
357 /*
358 * Use filesystem name if filesystems does not support rename()
359 * operation.
360 */
361 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
362 if (inode->i_op && !inode->i_op->rename)
363 goto prepend_filesystem_name;
364 #elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
365 if (!inode->i_op->rename && !inode->i_op->rename2)
366 goto prepend_filesystem_name;
367 #else
368 if (!inode->i_op->rename)
369 goto prepend_filesystem_name;
370 #endif
371 }
372 /* Prepend device name. */
373 {
374 char name[64];
375 int name_len;
376 const dev_t dev = sb->s_dev;
377
378 name[sizeof(name) - 1] = '\0';
379 snprintf(name, sizeof(name) - 1, "dev(%u,%u):", MAJOR(dev),
380 MINOR(dev));
381 name_len = strlen(name);
382 pos -= name_len;
383 if (pos < buffer)
384 goto out;
385 memmove(pos, name, name_len);
386 return pos;
387 }
388 /* Prepend filesystem name. */
389 prepend_filesystem_name:
390 {
391 const char *name = sb->s_type->name;
392 const int name_len = strlen(name);
393
394 pos -= name_len + 1;
395 if (pos < buffer)
396 goto out;
397 memmove(pos, name, name_len);
398 pos[name_len] = ':';
399 }
400 return pos;
401 out:
402 return ERR_PTR(-ENOMEM);
403 }
404
405 /**
406 * cs_realpath - Returns realpath(3) of the given pathname but ignores chroot'ed root.
407 *
408 * @path: Pointer to "struct path".
409 *
410 * Returns the realpath of the given @path on success, NULL otherwise.
411 *
412 * This function uses kzalloc(), so caller must kfree() if this function
413 * didn't return NULL.
414 */
415 char *cs_realpath(const struct path *path)
416 {
417 char *buf = NULL;
418 char *name = NULL;
419 unsigned int buf_len = PAGE_SIZE / 2;
420 struct dentry *dentry = path->dentry;
421 struct super_block *sb;
422
423 if (!dentry)
424 return NULL;
425 sb = dentry->d_sb;
426 while (1) {
427 char *pos;
428 struct inode *inode;
429
430 buf_len <<= 1;
431 kfree(buf);
432 buf = kmalloc(buf_len, GFP_NOFS);
433 if (!buf)
434 break;
435 /* To make sure that pos is '\0' terminated. */
436 buf[buf_len - 1] = '\0';
437 /* For "pipe:[\$]" and "socket:[\$]". */
438 if (dentry->d_op && dentry->d_op->d_dname) {
439 pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1);
440 goto encode;
441 }
442 inode = d_backing_inode(sb->s_root);
443 /*
444 * Use local name for "filesystems without rename() operation
445 * and device file" or "path without vfsmount" or "absolute
446 * name is unavailable" cases.
447 */
448 #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
449 if (!path->mnt ||
450 (inode->i_op && !inode->i_op->rename &&
451 !(sb->s_type->fs_flags & FS_REQUIRES_DEV)))
452 pos = ERR_PTR(-EINVAL);
453 else {
454 /* Get absolute name for the rest. */
455 cs_realpath_lock();
456 pos = cs_get_absolute_path(path, buf, buf_len - 1);
457 cs_realpath_unlock();
458 }
459 #elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 9, 0)
460 if (!path->mnt ||
461 (!inode->i_op->rename && !inode->i_op->rename2 &&
462 !(sb->s_type->fs_flags & FS_REQUIRES_DEV)))
463 pos = ERR_PTR(-EINVAL);
464 else
465 pos = cs_get_absolute_path(path, buf, buf_len - 1);
466 #else
467 if (!path->mnt ||
468 (!inode->i_op->rename &&
469 !(sb->s_type->fs_flags & FS_REQUIRES_DEV)))
470 pos = ERR_PTR(-EINVAL);
471 else
472 pos = cs_get_absolute_path(path, buf, buf_len - 1);
473 #endif
474 if (pos == ERR_PTR(-EINVAL))
475 pos = cs_get_local_path(path->dentry, buf,
476 buf_len - 1);
477 encode:
478 if (IS_ERR(pos))
479 continue;
480 name = cs_encode(pos);
481 break;
482 }
483 kfree(buf);
484 if (!name)
485 cs_warn_oom(__func__);
486 return name;
487 }
488
489 /**
490 * cs_encode2 - Encode binary string to ascii string.
491 *
492 * @str: String in binary format. Maybe NULL.
493 * @str_len: Size of @str in byte.
494 *
495 * Returns pointer to @str in ascii format on success, NULL otherwise.
496 *
497 * This function uses kzalloc(), so caller must kfree() if this function
498 * didn't return NULL.
499 */
500 char *cs_encode2(const char *str, int str_len)
501 {
502 int i;
503 int len;
504 const char *p = str;
505 char *cp;
506 char *cp0;
507
508 if (!p)
509 return NULL;
510 len = str_len;
511 for (i = 0; i < str_len; i++) {
512 const unsigned char c = p[i];
513
514 if (!(c > ' ' && c < 127 && c != '\\'))
515 len += 3;
516 }
517 len++;
518 cp = kzalloc(len, GFP_NOFS);
519 if (!cp)
520 return NULL;
521 cp0 = cp;
522 p = str;
523 for (i = 0; i < str_len; i++) {
524 const unsigned char c = p[i];
525
526 if (c > ' ' && c < 127 && c != '\\') {
527 *cp++ = c;
528 } else {
529 *cp++ = '\\';
530 *cp++ = (c >> 6) + '0';
531 *cp++ = ((c >> 3) & 7) + '0';
532 *cp++ = (c & 7) + '0';
533 }
534 }
535 return cp0;
536 }
537
538 /**
539 * cs_encode - Encode binary string to ascii string.
540 *
541 * @str: String in binary format. Maybe NULL.
542 *
543 * Returns pointer to @str in ascii format on success, NULL otherwise.
544 *
545 * This function uses kzalloc(), so caller must kfree() if this function
546 * didn't return NULL.
547 */
548 char *cs_encode(const char *str)
549 {
550 return str ? cs_encode2(str, strlen(str)) : NULL;
551 }
552
553 /**
554 * cs_const_part_length - Evaluate the initial length without a pattern in a token.
555 *
556 * @filename: The string to evaluate. Maybe NULL.
557 *
558 * Returns the initial length without a pattern in @filename.
559 */
560 static int cs_const_part_length(const char *filename)
561 {
562 char c;
563 int len = 0;
564
565 if (!filename)
566 return 0;
567 while (1) {
568 c = *filename++;
569 if (!c)
570 break;
571 if (c != '\\') {
572 len++;
573 continue;
574 }
575 c = *filename++;
576 switch (c) {
577 case '0': /* "\ooo" */
578 case '1':
579 case '2':
580 case '3':
581 c = *filename++;
582 if (c < '0' || c > '7')
583 break;
584 c = *filename++;
585 if (c < '0' || c > '7')
586 break;
587 len += 4;
588 continue;
589 }
590 break;
591 }
592 return len;
593 }
594
595 /**
596 * cs_fill_path_info - Fill in "struct cs_path_info" members.
597 *
598 * @ptr: Pointer to "struct cs_path_info" to fill in.
599 *
600 * Returns nothing.
601 *
602 * The caller sets "struct cs_path_info"->name.
603 */
604 void cs_fill_path_info(struct cs_path_info *ptr)
605 {
606 const char *name = ptr->name;
607 const int len = strlen(name);
608
609 ptr->total_len = len;
610 ptr->const_len = cs_const_part_length(name);
611 #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 8, 0)
612 ptr->hash = full_name_hash(NULL, name, len);
613 #else
614 ptr->hash = full_name_hash(name, len);
615 #endif
616 }
617
618 /**
619 * cs_get_exe - Get cs_realpath() of current process.
620 *
621 * Returns the cs_realpath() of current process on success, NULL otherwise.
622 *
623 * This function uses kzalloc(), so the caller must kfree()
624 * if this function didn't return NULL.
625 */
626 char *cs_get_exe(void)
627 {
628 struct mm_struct *mm;
629 #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 26)
630 struct vm_area_struct *vma;
631 #endif
632 struct file *exe_file;
633
634 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 27)
635 if (current->flags & PF_KTHREAD)
636 return kstrdup("<kernel>", GFP_NOFS);
637 #else
638 if (segment_eq(get_fs(), KERNEL_DS))
639 return kstrdup("<kernel>", GFP_NOFS);
640 #endif
641 mm = current->mm;
642 if (!mm)
643 goto task_has_no_mm;
644 #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
645 /* Not using get_mm_exe_file() as it is not exported. */
646 rcu_read_lock();
647 exe_file = rcu_dereference(mm->exe_file);
648 #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 7, 0)
649 if (exe_file && !get_file_rcu(&exe_file))
650 exe_file = NULL;
651 #else
652 if (exe_file && !get_file_rcu(exe_file))
653 exe_file = NULL;
654 #endif
655 rcu_read_unlock();
656 #elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 0)
657 exe_file = get_mm_exe_file(mm);
658 #else
659 down_read(&mm->mmap_sem);
660 #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26)
661 /* Not using get_mm_exe_file() as it is not exported. */
662 exe_file = mm->exe_file;
663 #else
664 exe_file = NULL;
665 for (vma = mm->mmap; vma; vma = vma->vm_next) {
666 if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) {
667 exe_file = vma->vm_file;
668 break;
669 }
670 }
671 #endif
672 if (exe_file)
673 get_file(exe_file);
674 up_read(&mm->mmap_sem);
675 #endif
676 if (exe_file) {
677 char *cp = cs_realpath(&exe_file->f_path);
678
679 fput(exe_file);
680 return cp;
681 }
682 task_has_no_mm:
683 /* I'don't know. */
684 return kstrdup("<unknown>", GFP_NOFS);
685 }
686
687 /**
688 * cs_get_exename - Get cs_realpath() of current process.
689 *
690 * @buf: Pointer to "struct cs_path_info".
691 *
692 * Returns true on success, false otherwise.
693 *
694 * This function uses kzalloc(), so the caller must kfree()
695 * if this function returned true.
696 */
697 bool cs_get_exename(struct cs_path_info *buf)
698 {
699 buf->name = cs_get_exe();
700 if (buf->name) {
701 cs_fill_path_info(buf);
702 return true;
703 }
704 return false;
705 }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26