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
|
* prepend_path - Prepend path string to a buffer
|
||||||
* @path: the dentry/vfsmount to report
|
* @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
|
* @buffer: pointer to the end of the buffer
|
||||||
* @buflen: pointer to buffer length
|
* @buflen: pointer to buffer length
|
||||||
*
|
*
|
||||||
* Caller holds the rename_lock.
|
* 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)
|
char **buffer, int *buflen)
|
||||||
{
|
{
|
||||||
struct dentry *dentry = path->dentry;
|
struct dentry *dentry = path->dentry;
|
||||||
@ -2483,10 +2481,10 @@ static int prepend_path(const struct path *path, struct path *root,
|
|||||||
dentry = parent;
|
dentry = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
out:
|
|
||||||
if (!error && !slash)
|
if (!error && !slash)
|
||||||
error = prepend(buffer, buflen, "/", 1);
|
error = prepend(buffer, buflen, "/", 1);
|
||||||
|
|
||||||
|
out:
|
||||||
br_read_unlock(vfsmount_lock);
|
br_read_unlock(vfsmount_lock);
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
@ -2500,15 +2498,17 @@ global_root:
|
|||||||
WARN(1, "Root dentry has weird name <%.*s>\n",
|
WARN(1, "Root dentry has weird name <%.*s>\n",
|
||||||
(int) dentry->d_name.len, dentry->d_name.name);
|
(int) dentry->d_name.len, dentry->d_name.name);
|
||||||
}
|
}
|
||||||
root->mnt = vfsmnt;
|
if (!slash)
|
||||||
root->dentry = dentry;
|
error = prepend(buffer, buflen, "/", 1);
|
||||||
|
if (!error)
|
||||||
|
error = vfsmnt->mnt_ns ? 1 : 2;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* __d_path - return the path of a dentry
|
* __d_path - return the path of a dentry
|
||||||
* @path: the dentry/vfsmount to report
|
* @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
|
* @buf: buffer to return value in
|
||||||
* @buflen: buffer length
|
* @buflen: buffer length
|
||||||
*
|
*
|
||||||
@ -2519,10 +2519,10 @@ global_root:
|
|||||||
*
|
*
|
||||||
* "buflen" should be positive.
|
* "buflen" should be positive.
|
||||||
*
|
*
|
||||||
* If path is not reachable from the supplied root, then the value of
|
* If the path is not reachable from the supplied root, return %NULL.
|
||||||
* root is changed (without modifying refcounts).
|
|
||||||
*/
|
*/
|
||||||
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 *buf, int buflen)
|
||||||
{
|
{
|
||||||
char *res = buf + 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);
|
error = prepend_path(path, root, &res, &buflen);
|
||||||
write_sequnlock(&rename_lock);
|
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 ERR_PTR(error);
|
||||||
return res;
|
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.
|
* same as __d_path but appends "(deleted)" for unlinked files.
|
||||||
*/
|
*/
|
||||||
static int path_with_deleted(const struct path *path, struct path *root,
|
static int path_with_deleted(const struct path *path,
|
||||||
char **buf, int *buflen)
|
const struct path *root,
|
||||||
|
char **buf, int *buflen)
|
||||||
{
|
{
|
||||||
prepend(buf, buflen, "\0", 1);
|
prepend(buf, buflen, "\0", 1);
|
||||||
if (d_unlinked(path->dentry)) {
|
if (d_unlinked(path->dentry)) {
|
||||||
@ -2579,7 +2601,6 @@ char *d_path(const struct path *path, char *buf, int buflen)
|
|||||||
{
|
{
|
||||||
char *res = buf + buflen;
|
char *res = buf + buflen;
|
||||||
struct path root;
|
struct path root;
|
||||||
struct path tmp;
|
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -2594,9 +2615,8 @@ char *d_path(const struct path *path, char *buf, int buflen)
|
|||||||
|
|
||||||
get_fs_root(current->fs, &root);
|
get_fs_root(current->fs, &root);
|
||||||
write_seqlock(&rename_lock);
|
write_seqlock(&rename_lock);
|
||||||
tmp = root;
|
error = path_with_deleted(path, &root, &res, &buflen);
|
||||||
error = path_with_deleted(path, &tmp, &res, &buflen);
|
if (error < 0)
|
||||||
if (error)
|
|
||||||
res = ERR_PTR(error);
|
res = ERR_PTR(error);
|
||||||
write_sequnlock(&rename_lock);
|
write_sequnlock(&rename_lock);
|
||||||
path_put(&root);
|
path_put(&root);
|
||||||
@ -2617,7 +2637,6 @@ char *d_path_with_unreachable(const struct path *path, char *buf, int buflen)
|
|||||||
{
|
{
|
||||||
char *res = buf + buflen;
|
char *res = buf + buflen;
|
||||||
struct path root;
|
struct path root;
|
||||||
struct path tmp;
|
|
||||||
int error;
|
int error;
|
||||||
|
|
||||||
if (path->dentry->d_op && path->dentry->d_op->d_dname)
|
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);
|
get_fs_root(current->fs, &root);
|
||||||
write_seqlock(&rename_lock);
|
write_seqlock(&rename_lock);
|
||||||
tmp = root;
|
error = path_with_deleted(path, &root, &res, &buflen);
|
||||||
error = path_with_deleted(path, &tmp, &res, &buflen);
|
if (error > 0)
|
||||||
if (!error && !path_equal(&tmp, &root))
|
|
||||||
error = prepend_unreachable(&res, &buflen);
|
error = prepend_unreachable(&res, &buflen);
|
||||||
write_sequnlock(&rename_lock);
|
write_sequnlock(&rename_lock);
|
||||||
path_put(&root);
|
path_put(&root);
|
||||||
@ -2758,19 +2776,18 @@ SYSCALL_DEFINE2(getcwd, char __user *, buf, unsigned long, size)
|
|||||||
write_seqlock(&rename_lock);
|
write_seqlock(&rename_lock);
|
||||||
if (!d_unlinked(pwd.dentry)) {
|
if (!d_unlinked(pwd.dentry)) {
|
||||||
unsigned long len;
|
unsigned long len;
|
||||||
struct path tmp = root;
|
|
||||||
char *cwd = page + PAGE_SIZE;
|
char *cwd = page + PAGE_SIZE;
|
||||||
int buflen = PAGE_SIZE;
|
int buflen = PAGE_SIZE;
|
||||||
|
|
||||||
prepend(&cwd, &buflen, "\0", 1);
|
prepend(&cwd, &buflen, "\0", 1);
|
||||||
error = prepend_path(&pwd, &tmp, &cwd, &buflen);
|
error = prepend_path(&pwd, &root, &cwd, &buflen);
|
||||||
write_sequnlock(&rename_lock);
|
write_sequnlock(&rename_lock);
|
||||||
|
|
||||||
if (error)
|
if (error < 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
/* Unreachable from current root */
|
/* Unreachable from current root */
|
||||||
if (!path_equal(&tmp, &root)) {
|
if (error > 0) {
|
||||||
error = prepend_unreachable(&cwd, &buflen);
|
error = prepend_unreachable(&cwd, &buflen);
|
||||||
if (error)
|
if (error)
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -1048,15 +1048,12 @@ static int show_mountinfo(struct seq_file *m, void *v)
|
|||||||
if (err)
|
if (err)
|
||||||
goto out;
|
goto out;
|
||||||
seq_putc(m, ' ');
|
seq_putc(m, ' ');
|
||||||
seq_path_root(m, &mnt_path, &root, " \t\n\\");
|
|
||||||
if (root.mnt != p->root.mnt || root.dentry != p->root.dentry) {
|
/* mountpoints outside of chroot jail will give SEQ_SKIP on this */
|
||||||
/*
|
err = seq_path_root(m, &mnt_path, &root, " \t\n\\");
|
||||||
* Mountpoint is outside root, discard that one. Ugly,
|
if (err)
|
||||||
* but less so than trying to do that in iterator in a
|
goto out;
|
||||||
* race-free way (due to renames).
|
|
||||||
*/
|
|
||||||
return SEQ_SKIP;
|
|
||||||
}
|
|
||||||
seq_puts(m, mnt->mnt_flags & MNT_READONLY ? " ro" : " rw");
|
seq_puts(m, mnt->mnt_flags & MNT_READONLY ? " ro" : " rw");
|
||||||
show_mnt_opts(m, mnt);
|
show_mnt_opts(m, mnt);
|
||||||
|
|
||||||
@ -2776,3 +2773,8 @@ void kern_unmount(struct vfsmount *mnt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(kern_unmount);
|
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.
|
* 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,
|
int seq_path_root(struct seq_file *m, struct path *path, struct path *root,
|
||||||
char *esc)
|
char *esc)
|
||||||
@ -463,6 +461,8 @@ int seq_path_root(struct seq_file *m, struct path *path, struct path *root,
|
|||||||
char *p;
|
char *p;
|
||||||
|
|
||||||
p = __d_path(path, root, buf, size);
|
p = __d_path(path, root, buf, size);
|
||||||
|
if (!p)
|
||||||
|
return SEQ_SKIP;
|
||||||
res = PTR_ERR(p);
|
res = PTR_ERR(p);
|
||||||
if (!IS_ERR(p)) {
|
if (!IS_ERR(p)) {
|
||||||
char *end = mangle_path(buf, p, esc);
|
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);
|
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 *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(const struct path *, char *, int);
|
||||||
extern char *d_path_with_unreachable(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);
|
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 statfs_by_dentry(struct dentry *, struct kstatfs *);
|
||||||
extern int freeze_super(struct super_block *super);
|
extern int freeze_super(struct super_block *super);
|
||||||
extern int thaw_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);
|
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,
|
static int d_namespace_path(struct path *path, char *buf, int buflen,
|
||||||
char **name, int flags)
|
char **name, int flags)
|
||||||
{
|
{
|
||||||
struct path root, tmp;
|
|
||||||
char *res;
|
char *res;
|
||||||
int connected, error = 0;
|
int error = 0;
|
||||||
|
int connected = 1;
|
||||||
|
|
||||||
/* Get the root we want to resolve too, released below */
|
if (path->mnt->mnt_flags & MNT_INTERNAL) {
|
||||||
if (flags & PATH_CHROOT_REL) {
|
/* it's not mounted anywhere */
|
||||||
/* resolve paths relative to chroot */
|
res = dentry_path(path->dentry, buf, buflen);
|
||||||
get_fs_root(current->fs, &root);
|
*name = res;
|
||||||
} else {
|
if (IS_ERR(res)) {
|
||||||
/* resolve paths relative to namespace */
|
*name = buf;
|
||||||
root.mnt = current->nsproxy->mnt_ns->root;
|
return PTR_ERR(res);
|
||||||
root.dentry = root.mnt->mnt_root;
|
}
|
||||||
path_get(&root);
|
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;
|
/* resolve paths relative to chroot?*/
|
||||||
res = __d_path(path, &tmp, buf, buflen);
|
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;
|
*name = res;
|
||||||
/* handle error conditions - and still allow a partial path to
|
/* 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;
|
*name = buf;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
if (!our_mnt(path->mnt))
|
||||||
|
connected = 0;
|
||||||
|
|
||||||
|
ok:
|
||||||
/* Handle two cases:
|
/* Handle two cases:
|
||||||
* 1. A deleted dentry && profile is not allowing mediation of deleted
|
* 1. A deleted dentry && profile is not allowing mediation of deleted
|
||||||
* 2. On some filesystems, newly allocated dentries appear to the
|
* 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;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Determine if the path is connected to the expected root */
|
/* If the path is not connected to the expected root,
|
||||||
connected = tmp.dentry == root.dentry && tmp.mnt == root.mnt;
|
|
||||||
|
|
||||||
/* If the path is not connected,
|
|
||||||
* check if it is a sysctl and handle specially else remove any
|
* check if it is a sysctl and handle specially else remove any
|
||||||
* leading / that __d_path may have returned.
|
* leading / that __d_path may have returned.
|
||||||
* Unless
|
* Unless
|
||||||
@ -112,17 +133,9 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
|||||||
* namespace root.
|
* namespace root.
|
||||||
*/
|
*/
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
/* is the disconnect path a sysctl? */
|
if (!(flags & PATH_CONNECT_PATH) &&
|
||||||
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) &&
|
|
||||||
!(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
|
!(((flags & CHROOT_NSCONNECT) == CHROOT_NSCONNECT) &&
|
||||||
(tmp.mnt == current->nsproxy->mnt_ns->root &&
|
our_mnt(path->mnt))) {
|
||||||
tmp.dentry == tmp.mnt->mnt_root))) {
|
|
||||||
/* disconnected path, don't return pathname starting
|
/* disconnected path, don't return pathname starting
|
||||||
* with '/'
|
* with '/'
|
||||||
*/
|
*/
|
||||||
@ -133,8 +146,6 @@ static int d_namespace_path(struct path *path, char *buf, int buflen,
|
|||||||
}
|
}
|
||||||
|
|
||||||
out:
|
out:
|
||||||
path_put(&root);
|
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,9 +101,8 @@ static char *tomoyo_get_absolute_path(struct path *path, char * const buffer,
|
|||||||
{
|
{
|
||||||
char *pos = ERR_PTR(-ENOMEM);
|
char *pos = ERR_PTR(-ENOMEM);
|
||||||
if (buflen >= 256) {
|
if (buflen >= 256) {
|
||||||
struct path ns_root = { };
|
|
||||||
/* go to whatever namespace root we are under */
|
/* 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]) {
|
if (!IS_ERR(pos) && *pos == '/' && pos[1]) {
|
||||||
struct inode *inode = path->dentry->d_inode;
|
struct inode *inode = path->dentry->d_inode;
|
||||||
if (inode && S_ISDIR(inode->i_mode)) {
|
if (inode && S_ISDIR(inode->i_mode)) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user