This patch contains code for getting canonicalized pathnames used by TOMOYO. TOMOYO 2.2.0 uses __d_path() for getting canonicalized pathnames, but there are several issues. (1) The " (deleted)" is appended to the returned pathname if the pathname was deleted. This causes TOMOYO to check allow_truncate /tmp/file for int fd = open("/tmp/file", O_WRONLY | O_CREAT, 0600); ftruncate(fd, 0); unlink("/tmp/file"); close(fd); sequence and allow_truncate /tmp/file\040(deleted) for int fd = open("/tmp/file", O_WRONLY | O_CREAT, 0600); unlink("/tmp/file"); ftruncate(fd, 0); close(fd); sequence, although these sequences are doing the same operations in different order. Since this is annoying for policy developers, I want to avoid trailing " (deleted)". (2) __d_path() does not replace /proc/PID/ with /proc/self/ if PID is equals to the current process's PID. Pathname based access control can prevent current process from accessing other process's information if we can map /proc/PID/ to /proc/self/ . The caller of __d_path() can't replace "PID" with "self" after returning from __d_path() because the caller cannot tell whether the numeric part of the returned pathname is a PID or not. To tell whether the numeric part of the returned pathname is a PID or not, the caller has to do dentry/vfsmount traversal by its own, and that makes use of __d_path() meaningless. (3) The pathname for socket is in "socket:[%u]" format. But such pathname is too useless for pathname based access control. I want to use pathnames in "socket:[family=%u:type=%u:protocol=%u]" format. The best pathname might include socket's peer address information, but it would be difficult to fetch and represent address information. To solve these issues, I'd like to introduce custom __d_path() functions (or add some flags to __d_path()). Signed-off-by: Tetsuo Handa --- security/tomoyo/new-realpath.c | 251 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) --- /dev/null +++ security-testing-2.6/security/tomoyo/new-realpath.c @@ -0,0 +1,251 @@ +/* + * security/tomoyo/realpath.c + * + * Copyright (C) 2005-2009 NTT DATA CORPORATION + */ +#include "internal.h" +#include +#include +#include +#include + +/** + * tomoyo_get_absolute_path - Get the path of a dentry but ignores chroot'ed root. + * + * @path: Pointer to "struct path". + * @buffer: Pointer to buffer to return value in. + * @buflen: Sizeof @buffer. + * + * Returns the buffer on success, an error code otherwise. + * + * Caller holds the dcache_lock and vfsmount_lock. + * Based on __d_path() in fs/dcache.c + * + * If dentry is a directory, trailing '/' is appended. + * /proc/pid is represented as /proc/self if pid is current. + */ +static char *tomoyo_get_absolute_path(struct path *path, char * const buffer, + const int buflen) +{ + char *pos = buffer + buflen - 1; + struct dentry *dentry = path->dentry; + struct vfsmount *vfsmnt = path->mnt; + bool is_dir = (dentry->d_inode && S_ISDIR(dentry->d_inode->i_mode)); + const char *name; + int len; + + if (buflen < 256) + goto out; + + *pos = '\0'; + for (;;) { + struct dentry *parent; + if (dentry == vfsmnt->mnt_root || IS_ROOT(dentry)) { + if (vfsmnt->mnt_parent == vfsmnt) + break; + dentry = vfsmnt->mnt_mountpoint; + vfsmnt = vfsmnt->mnt_parent; + continue; + } + if (is_dir) { + is_dir = false; + *--pos = '/'; + } + parent = dentry->d_parent; + name = dentry->d_name.name; + len = dentry->d_name.len; + if (IS_ROOT(parent) && *name > '0' && *name <= '9' && + parent->d_sb && + parent->d_sb->s_magic == PROC_SUPER_MAGIC) { + char *ep; + const pid_t pid = (pid_t) simple_strtoul(name, &ep, + 10); + const pid_t tgid = task_tgid_nr_ns(current, + dentry->d_sb-> + s_fs_info); + if (!*ep && pid == tgid && tgid) { + name = "self"; + len = 4; + } + } + pos -= len; + if (pos <= buffer) + goto out; + memmove(pos, name, len); + *--pos = '/'; + dentry = parent; + } + if (*pos == '/') + pos++; + len = dentry->d_name.len; + pos -= len; + if (pos < buffer) + goto out; + memmove(pos, dentry->d_name.name, len); + return pos; + out: + return ERR_PTR(-ENOMEM); +} + +#define SOCKFS_MAGIC 0x534F434B + +/** + * tomoyo_realpath_from_path - Returns realpath(3) of the given pathname but ignores chroot'ed root. + * + * @path: Pointer to "struct path". + * + * Returns the realpath of the given @path on success, NULL otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +char *tomoyo_realpath_from_path(struct path *path) +{ + char *buf = NULL; + char *name = NULL; + unsigned int buf_len = PAGE_SIZE / 2; + struct dentry *dentry = path->dentry; + if (!dentry) + return NULL; + while (1) { + char *pos; + buf_len <<= 1; + kfree(buf); + buf = kmalloc(buf_len, GFP_KERNEL); + if (!buf) + break; + /* Get better name for socket. */ + if (dentry->d_sb && dentry->d_sb->s_magic == SOCKFS_MAGIC) { + struct inode *inode = dentry->d_inode; + struct socket *sock = inode ? SOCKET_I(inode) : NULL; + struct sock *sk = sock ? sock->sk : NULL; + if (sk) { + snprintf(buf, buf_len - 1, "socket:[family=%u:" + "type=%u:protocol=%u]", sk->sk_family, + sk->sk_type, sk->sk_protocol); + } else { + snprintf(buf, buf_len - 1, "socket:[unknown]"); + } + name = tomoyo_encode(buf); + break; + } + /* For "socket:[\$]" and "pipe:[\$]". */ + if (dentry->d_op && dentry->d_op->d_dname) { + pos = dentry->d_op->d_dname(dentry, buf, buf_len - 1); + if (IS_ERR(pos)) + continue; + name = tomoyo_encode(pos); + break; + } + if (!path->mnt) + break; + path_get(path); + spin_lock(&dcache_lock); + spin_lock(&vfsmount_lock); + pos = tomoyo_get_absolute_path(path, buf, buf_len - 1); + spin_unlock(&vfsmount_lock); + spin_unlock(&dcache_lock); + path_put(path); + if (IS_ERR(pos)) + continue; + name = tomoyo_encode(pos); + break; + } + kfree(buf); + if (!name) + tomoyo_warn_oom(__func__); + return name; +} + +/** + * tomoyo_symlink_path - Get symlink's pathname. + * + * @pathname: The pathname to solve. + * @name: Pointer to "struct tomoyo_path_info". + * + * Returns 0 on success, negative value otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +int tomoyo_symlink_path(const char *pathname, struct tomoyo_path_info *name) +{ + char *buf; + struct path path; + if (!pathname || kern_path(pathname, 0, &path)) + return -ENOENT; + buf = tomoyo_realpath_from_path(&path); + path_put(&path); + if (buf) { + name->name = buf; + tomoyo_fill_path_info(name); + return 0; + } + return -ENOMEM; +} + +/** + * tomoyo_encode: Encode binary string to ascii string. + * + * @str: String in binary format. + * + * Returns pointer to @str in ascii format on success, NULL otherwise. + * + * This function uses kzalloc(), so caller must kfree() if this function + * didn't return NULL. + */ +char *tomoyo_encode(const char *str) +{ + int len = 0; + const char *p = str; + char *cp; + char *cp0; + if (!p) + return NULL; + while (*p) { + const unsigned char c = *p++; + if (c == '\\') + len += 2; + else if (c > ' ' && c < 127) + len++; + else + len += 4; + } + len++; + /* Reserve space for appending "/". */ + cp = kzalloc(len + 10, GFP_KERNEL); + if (!cp) + return NULL; + cp0 = cp; + p = str; + while (*p) { + const unsigned char c = *p++; + if (c == '\\') { + *cp++ = '\\'; + *cp++ = '\\'; + } else if (c > ' ' && c < 127) { + *cp++ = c; + } else { + *cp++ = '\\'; + *cp++ = (c >> 6) + '0'; + *cp++ = ((c >> 3) & 7) + '0'; + *cp++ = (c & 7) + '0'; + } + } + return cp0; +} + +/** + * tomoyo_get_path - Get dentry/vfsmmount of a pathname. + * + * @pathname: The pathname to solve. + * @path: Pointer to "struct path". + * + * Returns 0 on success, negative value otherwise. + */ +int tomoyo_get_path(const char *pathname, struct path *path) +{ + if (!pathname || kern_path(pathname, LOOKUP_FOLLOW, path)) + return -ENOENT; + return 0; +} -- -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/