forked from Minki/linux
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs: fix apparmor dereferencing potentially freed dentry, sanitize __d_path() API
This commit is contained in:
commit
3172f8fe1c
71
fs/dcache.c
71
fs/dcache.c
@ -2439,16 +2439,14 @@ static int prepend_name(char **buffer, int *buflen, struct qstr *name)
|
||||
/**
|
||||
* prepend_path - Prepend path string to a buffer
|
||||
* @path: the dentry/vfsmount to report
|
||||
* @root: root vfsmnt/dentry (may be modified by this function)
|
||||
* @root: root vfsmnt/dentry
|
||||
* @buffer: pointer to the end of the buffer
|
||||
* @buflen: pointer to buffer length
|
||||
*
|
||||
* Caller holds the rename_lock.
|
||||
*
|
||||
* If path is not reachable from the supplied root, then the value of
|
||||
* root is changed (without modifying refcounts).
|
||||
*/
|
||||
static int prepend_path(const struct path *path, struct path *root,
|
||||
static int prepend_path(const struct path *path,
|
||||
const struct path *root,
|
||||
char **buffer, int *buflen)
|
||||
{
|
||||
struct dentry *dentry = path->dentry;
|
||||
@ -2483,10 +2481,10 @@ static int prepend_path(const struct path *path, struct path *root,
|
||||
dentry = parent;
|
||||
}
|
||||
|
||||
out:
|
||||
if (!error && !slash)
|
||||
error = prepend(buffer, buflen, "/", 1);
|
||||
|
||||
out:
|
||||
br_read_unlock(vfsmount_lock);
|
||||
return error;
|
||||
|
||||
@ -2500,15 +2498,17 @@ global_root:
|
||||
WARN(1, "Root dentry has weird name <%.*s>\n",
|
||||
(int) dentry->d_name.len, dentry->d_name.name);
|
||||
}
|
||||
root->mnt = vfsmnt;
|
||||
root->dentry = dentry;
|
||||
if (!slash)
|
||||
error = prepend(buffer, buflen, "/", 1);
|
||||
if (!error)
|
||||
error = vfsmnt->mnt_ns ? 1 : 2;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/**
|
||||
* __d_path - return the path of a dentry
|
||||
* @path: the dentry/vfsmount to report
|
||||
* @root: root vfsmnt/dentry (may be modified by this function)
|
||||
* @root: root vfsmnt/dentry
|
||||
* @buf: buffer to return value in
|
||||
* @buflen: buffer length
|
||||
*
|
||||
@ -2519,10 +2519,10 @@ global_root:
|
||||
*
|
||||
* "buflen" should be positive.
|
||||
*
|
||||
* If path is not reachable from the supplied root, then the value of
|
||||
* root is changed (without modifying refcounts).
|
||||
* If the path is not reachable from the supplied root, return %NULL.
|
||||
*/
|
||||
char *__d_path(const struct path *path, struct path *root,
|
||||
char *__d_path(const struct path *path,
|
||||
const struct path *root,
|
||||
char *buf, int buflen)
|
||||
{
|
||||
char *res = buf + buflen;
|
||||
@ -2533,7 +2533,28 @@ char *__d_path(const struct path *path, struct path *root,
|
||||
error = prepend_path(path, root, &res, &buflen);
|
||||
write_sequnlock(&rename_lock);
|
||||
|
||||
if (error)
|
||||
if (error < 0)
|
||||
return ERR_PTR(error);
|
||||
if (error > 0)
|
||||
return NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
char *d_absolute_path(const struct path *path,
|
||||
char *buf, int buflen)
|
||||
{
|
||||
struct path root = {};
|
||||
char *res = buf + buflen;
|
||||
int error;
|
||||
|
||||
prepend(&res, &buflen, "\0", 1);
|
||||
write_seqlock(&rename_lock);
|
||||
error = prepend_path(path, &root, &res, &buflen);
|
||||
write_sequnlock(&rename_lock);
|
||||
|
||||
if (error > 1)
|
||||
error = -EINVAL;
|
||||
if (error < 0)
|
||||
return ERR_PTR(error);
|
||||
return res;
|
||||
}
|
||||
@ -2541,8 +2562,9 @@ char *__d_path(const struct path *path, struct path *root,
|
||||
/*
|
||||
* same as __d_path but appends "(deleted)" for unlinked files.
|
||||
*/
|
||||
static int path_with_deleted(const struct path *path, struct path *root,
|
||||
char **buf, int *buflen)
|
||||
static int path_with_deleted(const struct path *path,
|
||||
const struct path *root,
|
||||
char **buf, int *buflen)
|
||||
{
|
||||
prepend(buf, buflen, "\0", 1);
|
||||
if (d_unlinked(path->dentry)) {
|
||||
@ -2579,7 +2601,6 @@ char *d_path(const struct path *path, char *buf, int buflen)
|
||||
{
|
||||
char *res = buf + buflen;
|
||||
struct path root;
|
||||
struct path tmp;
|
||||
int error;
|
||||
|
||||
/*
|
||||
@ -2594,9 +2615,8 @@ char *d_path(const struct path *path, char *buf, int buflen)
|
||||
|
||||
get_fs_root(current->fs, &root);
|
||||
write_seqlock(&rename_lock);
|
||||
tmp = root;
|
||||
error = path_with_deleted(path, &tmp, &res, &buflen);
|
||||
if (error)
|
||||
error = path_with_deleted(path, &root, &res, &buflen);
|
||||
if (error < 0)
|
||||
res = ERR_PTR(error);
|
||||
write_sequnlock(&rename_lock);
|
||||
path_put(&root);
|
||||
@ -2617,7 +2637,6 @@ char *d_path_with_unreachable(const struct path *path, char *buf, int buflen)
|
||||
{
|
||||
char *res = buf + buflen;
|
||||
struct path root;
|
||||
struct path tmp;
|
||||
int error;
|
||||
|
||||
if (path->dentry->d_op && path->dentry->d_op->d_dname)
|
||||
@ -2625,9 +2644,8 @@ char *d_path_with_unreachable(const struct path *path, char *buf, int buflen)
|
||||
|
||||
get_fs_root(current->fs, &root);
|
||||
write_seqlock(&rename_lock);
|
||||
tmp = root;
|
||||
error = path_with_deleted(path, &tmp, &res, &buflen);
|
||||
if (!error && !path_equal(&tmp, &root))
|
||||
error = path_with_deleted(path, &root, &res, &buflen);
|
||||
if (error > 0)
|
||||
error = prepend_unreachable(&res, &buflen);
|
||||
write_sequnlock(&rename_lock);
|
||||
path_put(&root);
|
||||
@ -2758,19 +2776,18 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
|
||||
write_seqlock(&rename_lock);
|
||||
if (!d_unlinked(pwd.dentry)) {
|
||||
unsigned long len;
|
||||
struct path tmp = root;
|
||||
char *cwd = page + PAGE_SIZE;
|
||||
int buflen = PAGE_SIZE;
|
||||
|
||||
prepend(&cwd, &buflen, "\0", 1);
|
||||
error = prepend_path(&pwd, &tmp, &cwd, &buflen);
|
||||
error = prepend_path(&pwd, &root, &cwd, &buflen);
|
||||
write_sequnlock(&rename_lock);
|
||||
|
||||
if (error)
|
||||
if (error < 0)
|
||||
goto out;
|
||||
|
||||
/* Unreachable from current root */
|
||||
if (!path_equal(&tmp, &root)) {
|
||||
if (error > 0) {
|
||||
error = prepend_unreachable(&cwd, &buflen);
|
||||
if (error)
|
||||
goto out;
|
||||
|
@ -1048,15 +1048,12 @@ static int show_mountinfo(struct seq_file *m, void *v)
|
||||
if (err)
|
||||
goto out;
|
||||
seq_putc(m, ' ');
|
||||
seq_path_root(m, &mnt_path, &root, " \t\n\\");
|
||||
if (root.mnt != p->root.mnt || root.dentry != p->root.dentry) {
|
||||
/*
|
||||
* Mountpoint is outside root, discard that one. Ugly,
|
||||
* but less so than trying to do that in iterator in a
|
||||
* race-free way (due to renames).
|
||||
*/
|
||||
return SEQ_SKIP;
|
||||
}
|
||||
|
||||
/* mountpoints outside of chroot jail will give SEQ_SKIP on this */
|
||||
err = seq_path_root(m, &mnt_path, &root, " \t\n\\");
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
seq_puts(m, mnt->mnt_flags & MNT_READONLY ? " ro" : " rw");
|
||||
show_mnt_opts(m, mnt);
|
||||
|
||||
@ -2776,3 +2773,8 @@ void kern_unmount(struct vfsmount *mnt)
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(kern_unmount);
|
||||
|
||||
bool our_mnt(struct vfsmount *mnt)
|
||||
{
|
||||
return check_mnt(mnt);
|
||||
}
|
||||
|
@ -449,8 +449,6 @@ EXPORT_SYMBOL(seq_path);
|
||||
|
||||
/*
|
||||
* Same as seq_path, but relative to supplied root.
|
||||
*
|
||||
* root may be changed, see __d_path().
|
||||
*/
|
||||
int seq_path_root(struct seq_file *m, struct path *path, struct path *root,
|
||||
char *esc)
|
||||
@ -463,6 +461,8 @@ int seq_path_root(struct seq_file *m, struct path *path, struct path *root,
|
||||
char *p;
|
||||
|
||||
p = __d_path(path, root, buf, size);
|
||||
if (!p)
|
||||
return SEQ_SKIP;
|
||||
res = PTR_ERR(p);
|
||||
if (!IS_ERR(p)) {
|
||||
char *end = mangle_path(buf, p, esc);
|
||||
@ -474,7 +474,7 @@ int seq_path_root(struct seq_file *m, struct path *path, struct path *root,
|
||||
}
|
||||
seq_commit(m, res);
|
||||
|
||||
return res < 0 ? res : 0;
|
||||
return res < 0 && res != -ENAMETOOLONG ? res : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -339,7 +339,8 @@ extern int d_validate(struct dentry *, struct dentry *);
|
||||
*/
|
||||
extern char *dynamic_dname(struct dentry *, char *, int, const char *, ...);
|
||||
|
||||
extern char *__d_path(const struct path *path, struct path *root, char *, int);
|
||||
extern char *__d_path(const struct path *, const struct path *, char *, int);
|
||||
extern char *d_absolute_path(const struct path *, char *, int);
|
||||
extern char *d_path(const struct path *, char *, int);
|
||||
extern char *d_path_with_unreachable(const struct path *, char *, int);
|
||||
extern char *dentry_path_raw(struct dentry *, char *, int);
|
||||
|
@ -1942,6 +1942,7 @@ extern int fd_statfs(int, struct kstatfs *);
|
||||
extern int statfs_by_dentry(struct dentry *, struct kstatfs *);
|
||||
extern int freeze_super(struct super_block *super);
|
||||
extern int thaw_super(struct super_block *super);
|
||||
extern bool our_mnt(struct vfsmount *mnt);
|
||||
|
||||
extern int current_umask(void);
|
||||
|
||||
|
@ -57,23 +57,44 @@ static int prepend(char **buffer, int buflen, const char *str, int namelen)
|
||||
static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||
char **name, int flags)
|
||||
{
|
||||
struct path root, tmp;
|
||||
char *res;
|
||||
int connected, error = 0;
|
||||
int error = 0;
|
||||
int connected = 1;
|
||||
|
||||
/* Get the root we want to resolve too, released below */
|
||||
if (flags & PATH_CHROOT_REL) {
|
||||
/* resolve paths relative to chroot */
|
||||
get_fs_root(current->fs, &root);
|
||||
} else {
|
||||
/* resolve paths relative to namespace */
|
||||
root.mnt = current->nsproxy->mnt_ns->root;
|
||||
root.dentry = root.mnt->mnt_root;
|
||||
path_get(&root);
|
||||
if (path->mnt->mnt_flags & MNT_INTERNAL) {
|
||||
/* it's not mounted anywhere */
|
||||
res = dentry_path(path->dentry, buf, buflen);
|
||||
*name = res;
|
||||
if (IS_ERR(res)) {
|
||||
*name = buf;
|
||||
return PTR_ERR(res);
|
||||
}
|
||||
if (path->dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
|
||||
strncmp(*name, "/sys/", 5) == 0) {
|
||||
/* TODO: convert over to using a per namespace
|
||||
* control instead of hard coded /proc
|
||||
*/
|
||||
return prepend(name, *name - buf, "/proc", 5);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
tmp = root;
|
||||
res = __d_path(path, &tmp, buf, buflen);
|
||||
/* resolve paths relative to chroot?*/
|
||||
if (flags & PATH_CHROOT_REL) {
|
||||
struct path root;
|
||||
get_fs_root(current->fs, &root);
|
||||
res = __d_path(path, &root, buf, buflen);
|
||||
if (res && !IS_ERR(res)) {
|
||||
/* everything's fine */
|
||||
*name = res;
|
||||
path_put(&root);
|
||||
goto ok;
|
||||
}
|
||||
path_put(&root);
|
||||
connected = 0;
|
||||
}
|
||||
|
||||
res = d_absolute_path(path, buf, buflen);
|
||||
|
||||
*name = res;
|
||||
/* handle error conditions - and still allow a partial path to
|
||||
@ -84,7 +105,10 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||
*name = buf;
|
||||
goto out;
|
||||
}
|
||||
if (!our_mnt(path->mnt))
|
||||
connected = 0;
|
||||
|
||||
ok:
|
||||
/* Handle two cases:
|
||||
* 1. A deleted dentry && profile is not allowing mediation of deleted
|
||||
* 2. On some filesystems, newly allocated dentries appear to the
|
||||
@ -97,10 +121,7 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Determine if the path is connected to the expected root */
|
||||
connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;
|
||||
|
||||
/* If the path is not connected,
|
||||
/* If the path is not connected to the expected root,
|
||||
* check if it is a sysctl and handle specially else remove any
|
||||
* leading / that __d_path may have returned.
|
||||
* Unless
|
||||
@ -112,17 +133,9 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||
* namespace root.
|
||||
*/
|
||||
if (!connected) {
|
||||
/* is the disconnect path a sysctl? */
|
||||
if (tmp.dentry->d_sb->s_magic == PROC_SUPER_MAGIC &&
|
||||
strncmp(*name, "/sys/", 5) == 0) {
|
||||
/* TODO: convert over to using a per namespace
|
||||
* control instead of hard coded /proc
|
||||
*/
|
||||
error = prepend(name, *name - buf, "/proc", 5);
|
||||
} else if (!(flags & PATH_CONNECT_PATH) &&
|
||||
if (!(flags & PATH_CONNECT_PATH) &&
|
||||
!(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
|
||||
(tmp.mnt == current->nsproxy->mnt_ns->root &&
|
||||
tmp.dentry == tmp.mnt->mnt_root))) {
|
||||
our_mnt(path->mnt))) {
|
||||
/* disconnected path, don't return pathname starting
|
||||
* with '/'
|
||||
*/
|
||||
@ -133,8 +146,6 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||
}
|
||||
|
||||
out:
|
||||
path_put(&root);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -101,9 +101,8 @@ static char *tomoyo_get_absolute_path(struct path *path, char * const buffer,
|
||||
{
|
||||
char *pos = ERR_PTR(-ENOMEM);
|
||||
if (buflen >= 256) {
|
||||
struct path ns_root = { };
|
||||
/* go to whatever namespace root we are under */
|
||||
pos = __d_path(path, &ns_root, buffer, buflen - 1);
|
||||
pos = d_absolute_path(path, buffer, buflen - 1);
|
||||
if (!IS_ERR(pos) && *pos == '/' && pos[1]) {
|
||||
struct inode *inode = path->dentry->d_inode;
|
||||
if (inode && S_ISDIR(inode->i_mode)) {
|
||||
|
Loading…
Reference in New Issue
Block a user