[PATCH 4/7] Security: Pass the union-layer file path into security_file_open()

From: David Howells
Date: Wed Nov 05 2014 - 10:43:12 EST


Pass the path point representing the union-layer file into security_file_open()
so that the correct security state can be divined - otherwise for overlayfs,
only the security state for the lower filesystem can be accessed.

This is a stopgap and isn't really the correct solution: the correct solution
is to make file->f_path point at the union-layer path point and file->f_inode
point at the lower file inode - but this requires the union dentry to pin the
lower dentry.

Signed-off-by: David Howells <dhowells@xxxxxxxxxx>
---

fs/ceph/file.c | 3 ++-
fs/ceph/super.h | 1 +
fs/cifs/cifsfs.h | 1 +
fs/cifs/dir.c | 3 ++-
fs/namei.c | 11 +++++++----
fs/nfs/dir.c | 6 ++++--
fs/nfs/nfs4_fs.h | 2 +-
fs/open.c | 31 ++++++++++++++++++++-----------
fs/overlayfs/inode.c | 8 +++++---
include/linux/fs.h | 16 +++++++++++++---
include/linux/security.h | 8 ++++++--
security/capability.c | 4 +++-
security/security.c | 6 ++++--
security/selinux/hooks.c | 3 ++-
14 files changed, 71 insertions(+), 32 deletions(-)

diff --git a/fs/ceph/file.c b/fs/ceph/file.c
index d7e0da8366e6..43abb473bf84 100644
--- a/fs/ceph/file.c
+++ b/fs/ceph/file.c
@@ -228,6 +228,7 @@ out:
* file or symlink, return 1 so the VFS can retry.
*/
int ceph_atomic_open(struct inode *dir, struct dentry *dentry,
+ const struct path *union_path,
struct file *file, unsigned flags, umode_t mode,
int *opened)
{
@@ -302,7 +303,7 @@ int ceph_atomic_open(struct inode *dir, struct dentry *dentry,
ceph_init_inode_acls(dentry->d_inode, &acls);
*opened |= FILE_CREATED;
}
- err = finish_open(file, dentry, ceph_open, opened);
+ err = finish_open(file, dentry, union_path, ceph_open, opened);
}
out_req:
if (!req->r_err && req->r_target_inode)
diff --git a/fs/ceph/super.h b/fs/ceph/super.h
index b82f507979b8..5120b1a04d33 100644
--- a/fs/ceph/super.h
+++ b/fs/ceph/super.h
@@ -849,6 +849,7 @@ extern const struct address_space_operations ceph_aops;

extern int ceph_open(struct inode *inode, struct file *file);
extern int ceph_atomic_open(struct inode *dir, struct dentry *dentry,
+ const struct path *union_path,
struct file *file, unsigned flags, umode_t mode,
int *opened);
extern int ceph_release(struct inode *inode, struct file *filp);
diff --git a/fs/cifs/cifsfs.h b/fs/cifs/cifsfs.h
index 002e0c173939..0ff63fcbecba 100644
--- a/fs/cifs/cifsfs.h
+++ b/fs/cifs/cifsfs.h
@@ -59,6 +59,7 @@ extern struct inode *cifs_root_iget(struct super_block *);
extern int cifs_create(struct inode *, struct dentry *, umode_t,
bool excl);
extern int cifs_atomic_open(struct inode *, struct dentry *,
+ const struct path *,
struct file *, unsigned, umode_t,
int *);
extern struct dentry *cifs_lookup(struct inode *, struct dentry *,
diff --git a/fs/cifs/dir.c b/fs/cifs/dir.c
index b72bc29cba23..356bbd981415 100644
--- a/fs/cifs/dir.c
+++ b/fs/cifs/dir.c
@@ -414,6 +414,7 @@ out:

int
cifs_atomic_open(struct inode *inode, struct dentry *direntry,
+ const struct path *union_path,
struct file *file, unsigned oflags, umode_t mode,
int *opened)
{
@@ -489,7 +490,7 @@ cifs_atomic_open(struct inode *inode, struct dentry *direntry,
if ((oflags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
*opened |= FILE_CREATED;

- rc = finish_open(file, direntry, generic_file_open, opened);
+ rc = finish_open(file, direntry, union_path, generic_file_open, opened);
if (rc) {
if (server->ops->close)
server->ops->close(xid, tcon, &fid);
diff --git a/fs/namei.c b/fs/namei.c
index 922f27068c4c..5d1e40c047f9 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2647,6 +2647,7 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
int *opened)
{
struct inode *dir = nd->path.dentry->d_inode;
+ struct path top;
unsigned open_flag = open_to_namei_flags(op->open_flag);
umode_t mode;
int error;
@@ -2712,10 +2713,12 @@ static int atomic_open(struct nameidata *nd, struct dentry *dentry,
if (nd->flags & LOOKUP_DIRECTORY)
open_flag |= O_DIRECTORY;

+ top.dentry = dentry;
+ top.mnt = nd->path.mnt;
file->f_path.dentry = DENTRY_NOT_SET;
file->f_path.mnt = nd->path.mnt;
- error = dir->i_op->atomic_open(dir, dentry, file, open_flag, mode,
- opened);
+ error = dir->i_op->atomic_open(dir, dentry, &top, file,
+ open_flag, mode, opened);
if (error < 0) {
if (create_error && error == -ENOENT)
error = create_error;
@@ -3062,7 +3065,7 @@ finish_open_created:
goto out;

BUG_ON(*opened & FILE_OPENED); /* once it's opened, it's opened */
- error = vfs_open(&nd->path, file, current_cred());
+ error = vfs_open(&nd->path, &nd->path, file, current_cred());
if (!error) {
*opened |= FILE_OPENED;
} else {
@@ -3158,7 +3161,7 @@ static int do_tmpfile(int dfd, struct filename *pathname,
if (error)
goto out2;
file->f_path.mnt = nd->path.mnt;
- error = finish_open(file, nd->path.dentry, NULL, opened);
+ error = finish_open(file, nd->path.dentry, &nd->path, NULL, opened);
if (error)
goto out2;
error = open_check_o_direct(file);
diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c
index 06e8cfcbb670..0fd37112dc87 100644
--- a/fs/nfs/dir.c
+++ b/fs/nfs/dir.c
@@ -1446,6 +1446,7 @@ static int do_open(struct inode *inode, struct file *filp)

static int nfs_finish_open(struct nfs_open_context *ctx,
struct dentry *dentry,
+ const struct path *union_path,
struct file *file, unsigned open_flags,
int *opened)
{
@@ -1454,7 +1455,7 @@ static int nfs_finish_open(struct nfs_open_context *ctx,
if ((open_flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL))
*opened |= FILE_CREATED;

- err = finish_open(file, dentry, do_open, opened);
+ err = finish_open(file, dentry, union_path, do_open, opened);
if (err)
goto out;
nfs_file_set_open_context(file, ctx);
@@ -1464,6 +1465,7 @@ out:
}

int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
+ const struct path *union_path,
struct file *file, unsigned open_flags,
umode_t mode, int *opened)
{
@@ -1542,7 +1544,7 @@ int nfs_atomic_open(struct inode *dir, struct dentry *dentry,
goto out;
}

- err = nfs_finish_open(ctx, ctx->dentry, file, open_flags, opened);
+ err = nfs_finish_open(ctx, ctx->dentry, union_path, file, open_flags, opened);
trace_nfs_atomic_open_exit(dir, ctx, open_flags, err);
put_nfs_open_context(ctx);
out:
diff --git a/fs/nfs/nfs4_fs.h b/fs/nfs/nfs4_fs.h
index be6cac37ea10..2b68a876780d 100644
--- a/fs/nfs/nfs4_fs.h
+++ b/fs/nfs/nfs4_fs.h
@@ -212,7 +212,7 @@ struct nfs4_mig_recovery_ops {
extern const struct dentry_operations nfs4_dentry_operations;

/* dir.c */
-int nfs_atomic_open(struct inode *, struct dentry *, struct file *,
+int nfs_atomic_open(struct inode *, struct dentry *, const struct path *, struct file *,
unsigned, umode_t, int *);

/* super.c */
diff --git a/fs/open.c b/fs/open.c
index de92c13b58be..0cb66c96924a 100644
--- a/fs/open.c
+++ b/fs/open.c
@@ -665,6 +665,7 @@ int open_check_o_direct(struct file *f)
}

static int do_dentry_open(struct file *f,
+ const struct path *union_path,
int (*open)(struct inode *, struct file *),
const struct cred *cred)
{
@@ -707,7 +708,7 @@ static int do_dentry_open(struct file *f,
goto cleanup_all;
}

- error = security_file_open(f, cred);
+ error = security_file_open(f, union_path, cred);
if (error)
goto cleanup_all;

@@ -755,6 +756,7 @@ cleanup_file:
* finish_open - finish opening a file
* @file: file pointer
* @dentry: pointer to dentry
+ * @union_path: path userspace actually asked for
* @open: open callback
* @opened: state of open
*
@@ -772,7 +774,9 @@ cleanup_file:
*
* Returns zero on success or -errno if the open failed.
*/
-int finish_open(struct file *file, struct dentry *dentry,
+int finish_open(struct file *file,
+ struct dentry *dentry,
+ const struct path *union_path,
int (*open)(struct inode *, struct file *),
int *opened)
{
@@ -780,7 +784,7 @@ int finish_open(struct file *file, struct dentry *dentry,
BUG_ON(*opened & FILE_OPENED); /* once it's opened, it's opened */

file->f_path.dentry = dentry;
- error = do_dentry_open(file, open, current_cred());
+ error = do_dentry_open(file, union_path, open, current_cred());
if (!error)
*opened |= FILE_OPENED;

@@ -792,7 +796,7 @@ EXPORT_SYMBOL(finish_open);
* finish_no_open - finish ->atomic_open() without opening the file
*
* @file: file pointer
- * @dentry: dentry or NULL (as returned from ->lookup())
+ * @path: The path of the file actually opened (as returned from ->lookup())
*
* This can be used to set the result of a successful lookup in ->atomic_open().
*
@@ -809,8 +813,9 @@ int finish_no_open(struct file *file, struct dentry *dentry)
}
EXPORT_SYMBOL(finish_no_open);

-struct file *dentry_open(const struct path *path, int flags,
- const struct cred *cred)
+struct file *_dentry_open(const struct path *path,
+ const struct path *union_path, int flags,
+ const struct cred *cred)
{
int error;
struct file *f;
@@ -823,7 +828,7 @@ struct file *dentry_open(const struct path *path, int flags,
f = get_empty_filp();
if (!IS_ERR(f)) {
f->f_flags = flags;
- error = vfs_open(path, f, cred);
+ error = vfs_open(path, union_path, f, cred);
if (!error) {
/* from now on we need fput() to dispose of f */
error = open_check_o_direct(f);
@@ -838,24 +843,28 @@ struct file *dentry_open(const struct path *path, int flags,
}
return f;
}
-EXPORT_SYMBOL(dentry_open);
+EXPORT_SYMBOL(_dentry_open);

/**
* vfs_open - open the file at the given path
* @path: path to open
+ * @union_path: path userspace actually asked for
* @filp: newly allocated file with f_flag initialized
* @cred: credentials to use
*/
-int vfs_open(const struct path *path, struct file *filp,
+int vfs_open(const struct path *path,
+ const struct path *union_path,
+ struct file *filp,
const struct cred *cred)
{
struct inode *inode = path->dentry->d_inode;

if (inode->i_op->dentry_open)
- return inode->i_op->dentry_open(path->dentry, filp, cred);
+ return inode->i_op->dentry_open(path->dentry,
+ union_path, filp, cred);
else {
filp->f_path = *path;
- return do_dentry_open(filp, NULL, cred);
+ return do_dentry_open(filp, union_path, NULL, cred);
}
}
EXPORT_SYMBOL(vfs_open);
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c
index af2d18c9fcee..87316e93dbfa 100644
--- a/fs/overlayfs/inode.c
+++ b/fs/overlayfs/inode.c
@@ -324,8 +324,10 @@ static bool ovl_open_need_copy_up(int flags, enum ovl_path_type type,
return true;
}

-static int ovl_dentry_open(struct dentry *dentry, struct file *file,
- const struct cred *cred)
+static int ovl_dentry_open(struct dentry *dentry,
+ const struct path *union_path,
+ struct file *file,
+ const struct cred *cred)
{
int err;
struct path realpath;
@@ -349,7 +351,7 @@ static int ovl_dentry_open(struct dentry *dentry, struct file *file,
ovl_path_upper(dentry, &realpath);
}

- err = vfs_open(&realpath, file, cred);
+ err = vfs_open(&realpath, union_path, file, cred);
out_drop_write:
if (want_write)
ovl_drop_write(dentry);
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 1c12c681803f..6f8768b3cad5 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1549,13 +1549,15 @@ struct inode_operations {
u64 len);
int (*update_time)(struct inode *, struct timespec *, int);
int (*atomic_open)(struct inode *, struct dentry *,
+ const struct path *,
struct file *, unsigned open_flag,
umode_t create_mode, int *opened);
int (*tmpfile) (struct inode *, struct dentry *, umode_t);
int (*set_acl)(struct inode *, struct posix_acl *, int);

/* WARNING: probably going away soon, do not use! */
- int (*dentry_open)(struct dentry *, struct file *, const struct cred *);
+ int (*dentry_open)(struct dentry *, const struct path *,
+ struct file *, const struct cred *);
} ____cacheline_aligned;

ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
@@ -2071,8 +2073,15 @@ extern struct file *file_open_name(struct filename *, int, umode_t);
extern struct file *filp_open(const char *, int, umode_t);
extern struct file *file_open_root(struct dentry *, struct vfsmount *,
const char *, int);
-extern int vfs_open(const struct path *, struct file *, const struct cred *);
-extern struct file * dentry_open(const struct path *, int, const struct cred *);
+extern int vfs_open(const struct path *, const struct path *,
+ struct file *, const struct cred *);
+extern struct file *_dentry_open(const struct path *, const struct path *,
+ int, const struct cred *);
+static inline struct file *dentry_open(const struct path *path,
+ int flags, const struct cred *cred)
+{
+ return _dentry_open(path, path, flags, cred);
+}
extern int filp_close(struct file *, fl_owner_t id);

extern struct filename *getname(const char __user *);
@@ -2083,6 +2092,7 @@ enum {
FILE_OPENED = 2
};
extern int finish_open(struct file *file, struct dentry *dentry,
+ const struct path *union_path,
int (*open)(struct inode *, struct file *),
int *opened);
extern int finish_no_open(struct file *file, struct dentry *dentry);
diff --git a/include/linux/security.h b/include/linux/security.h
index 637a24c75d46..78fa30f5d708 100644
--- a/include/linux/security.h
+++ b/include/linux/security.h
@@ -1584,7 +1584,9 @@ struct security_operations {
int (*file_send_sigiotask) (struct task_struct *tsk,
struct fown_struct *fown, int sig);
int (*file_receive) (struct file *file);
- int (*file_open) (struct file *file, const struct cred *cred);
+ int (*file_open) (struct file *file,
+ const struct path *union_path,
+ const struct cred *cred);

int (*task_create) (unsigned long clone_flags);
void (*task_free) (struct task_struct *task);
@@ -1863,7 +1865,8 @@ void security_file_set_fowner(struct file *file);
int security_file_send_sigiotask(struct task_struct *tsk,
struct fown_struct *fown, int sig);
int security_file_receive(struct file *file);
-int security_file_open(struct file *file, const struct cred *cred);
+int security_file_open(struct file *file, const struct path *union_path,
+ const struct cred *cred);
int security_task_create(unsigned long clone_flags);
void security_task_free(struct task_struct *task);
int security_cred_alloc_blank(struct cred *cred, gfp_t gfp);
@@ -2365,6 +2368,7 @@ static inline int security_file_receive(struct file *file)
}

static inline int security_file_open(struct file *file,
+ const struct path *union_path,
const struct cred *cred)
{
return 0;
diff --git a/security/capability.c b/security/capability.c
index 6b21615d1500..10dacb48ff53 100644
--- a/security/capability.c
+++ b/security/capability.c
@@ -370,7 +370,9 @@ static int cap_file_receive(struct file *file)
return 0;
}

-static int cap_file_open(struct file *file, const struct cred *cred)
+static int cap_file_open(struct file *file,
+ const struct path *union_path,
+ const struct cred *cred)
{
return 0;
}
diff --git a/security/security.c b/security/security.c
index 96e2f189ff1e..44b889a88d18 100644
--- a/security/security.c
+++ b/security/security.c
@@ -804,11 +804,13 @@ int security_file_receive(struct file *file)
return security_ops->file_receive(file);
}

-int security_file_open(struct file *file, const struct cred *cred)
+int security_file_open(struct file *file,
+ const struct path *union_path,
+ const struct cred *cred)
{
int ret;

- ret = security_ops->file_open(file, cred);
+ ret = security_ops->file_open(file, union_path, cred);
if (ret)
return ret;

diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c
index f3fe7dbbf741..6fd8090cc7a5 100644
--- a/security/selinux/hooks.c
+++ b/security/selinux/hooks.c
@@ -3431,7 +3431,8 @@ static int selinux_file_receive(struct file *file)
return file_has_perm(cred, file, file_to_av(file));
}

-static int selinux_file_open(struct file *file, const struct cred *cred)
+static int selinux_file_open(struct file *file, const struct path *union_path,
+ const struct cred *cred)
{
struct file_security_struct *fsec;
struct inode_security_struct *isec;

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/