#include <linux/fsnotify.h>
#include <linux/personality.h>
#include <linux/security.h>
+#include <linux/ima.h>
#include <linux/syscalls.h>
#include <linux/mount.h>
#include <linux/audit.h>
#include <linux/file.h>
#include <linux/fcntl.h>
#include <linux/device_cgroup.h>
+#include <linux/fs_struct.h>
#include <asm/uaccess.h>
#define ACC_MODE(x) ("\000\004\002\006"[(x)&O_ACCMODE])
EXPORT_SYMBOL(putname);
#endif
-
-/**
- * generic_permission - check for access rights on a Posix-like filesystem
- * @inode: inode to check access rights for
- * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC)
- * @check_acl: optional callback to check for Posix ACLs
- *
- * Used to check for read/write/execute permissions on a file.
- * We use "fsuid" for this, letting us set arbitrary permissions
- * for filesystem access without changing the "normal" uids which
- * are used for other things..
+/*
+ * This does basic POSIX ACL permission checking
*/
-int generic_permission(struct inode *inode, int mask,
+static int acl_permission_check(struct inode *inode, int mask,
int (*check_acl)(struct inode *inode, int mask))
{
umode_t mode = inode->i_mode;
else {
if (IS_POSIXACL(inode) && (mode & S_IRWXG) && check_acl) {
int error = check_acl(inode, mask);
- if (error == -EACCES)
- goto check_capabilities;
- else if (error != -EAGAIN)
+ if (error != -EAGAIN)
return error;
}
*/
if ((mask & ~mode) == 0)
return 0;
+ return -EACCES;
+}
+
+/**
+ * generic_permission - check for access rights on a Posix-like filesystem
+ * @inode: inode to check access rights for
+ * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC)
+ * @check_acl: optional callback to check for Posix ACLs
+ *
+ * Used to check for read/write/execute permissions on a file.
+ * We use "fsuid" for this, letting us set arbitrary permissions
+ * for filesystem access without changing the "normal" uids which
+ * are used for other things..
+ */
+int generic_permission(struct inode *inode, int mask,
+ int (*check_acl)(struct inode *inode, int mask))
+{
+ int ret;
+
+ /*
+ * Do the basic POSIX ACL permission checks.
+ */
+ ret = acl_permission_check(inode, mask, check_acl);
+ if (ret != -EACCES)
+ return ret;
- check_capabilities:
/*
* Read/write DACs are always overridable.
* Executable DACs are overridable if at least one exec bit is set.
if (inode->i_op->permission)
retval = inode->i_op->permission(inode, mask);
else
- retval = generic_permission(inode, mask, NULL);
+ retval = generic_permission(inode, mask, inode->i_op->check_acl);
if (retval)
return retval;
*/
static int exec_permission_lite(struct inode *inode)
{
- umode_t mode = inode->i_mode;
-
- if (inode->i_op->permission)
- return -EAGAIN;
+ int ret;
- if (current_fsuid() == inode->i_uid)
- mode >>= 6;
- else if (in_group_p(inode->i_gid))
- mode >>= 3;
-
- if (mode & MAY_EXEC)
- goto ok;
-
- if ((inode->i_mode & S_IXUGO) && capable(CAP_DAC_OVERRIDE))
- goto ok;
-
- if (S_ISDIR(inode->i_mode) && capable(CAP_DAC_OVERRIDE))
+ if (inode->i_op->permission) {
+ ret = inode->i_op->permission(inode, MAY_EXEC);
+ if (!ret)
+ goto ok;
+ return ret;
+ }
+ ret = acl_permission_check(inode, MAY_EXEC, inode->i_op->check_acl);
+ if (!ret)
goto ok;
- if (S_ISDIR(inode->i_mode) && capable(CAP_DAC_READ_SEARCH))
+ if (capable(CAP_DAC_OVERRIDE) || capable(CAP_DAC_READ_SEARCH))
goto ok;
- return -EACCES;
+ return ret;
ok:
return security_inode_permission(inode, MAY_EXEC);
}
return result;
}
+static __always_inline void set_root(struct nameidata *nd)
+{
+ if (!nd->root.mnt) {
+ struct fs_struct *fs = current->fs;
+ read_lock(&fs->lock);
+ nd->root = fs->root;
+ path_get(&nd->root);
+ read_unlock(&fs->lock);
+ }
+}
+
static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *link)
{
int res = 0;
goto fail;
if (*link == '/') {
- struct fs_struct *fs = current->fs;
-
+ set_root(nd);
path_put(&nd->path);
-
- read_lock(&fs->lock);
- nd->path = fs->root;
- path_get(&fs->root);
- read_unlock(&fs->lock);
+ nd->path = nd->root;
+ path_get(&nd->root);
}
res = link_path_walk(link, nd);
return err;
}
-int follow_up(struct vfsmount **mnt, struct dentry **dentry)
+int follow_up(struct path *path)
{
struct vfsmount *parent;
struct dentry *mountpoint;
spin_lock(&vfsmount_lock);
- parent=(*mnt)->mnt_parent;
- if (parent == *mnt) {
+ parent = path->mnt->mnt_parent;
+ if (parent == path->mnt) {
spin_unlock(&vfsmount_lock);
return 0;
}
mntget(parent);
- mountpoint=dget((*mnt)->mnt_mountpoint);
+ mountpoint = dget(path->mnt->mnt_mountpoint);
spin_unlock(&vfsmount_lock);
- dput(*dentry);
- *dentry = mountpoint;
- mntput(*mnt);
- *mnt = parent;
+ dput(path->dentry);
+ path->dentry = mountpoint;
+ mntput(path->mnt);
+ path->mnt = parent;
return 1;
}
{
int res = 0;
while (d_mountpoint(path->dentry)) {
- struct vfsmount *mounted = lookup_mnt(path->mnt, path->dentry);
+ struct vfsmount *mounted = lookup_mnt(path);
if (!mounted)
break;
dput(path->dentry);
return res;
}
-static void follow_mount(struct vfsmount **mnt, struct dentry **dentry)
+static void follow_mount(struct path *path)
{
- while (d_mountpoint(*dentry)) {
- struct vfsmount *mounted = lookup_mnt(*mnt, *dentry);
+ while (d_mountpoint(path->dentry)) {
+ struct vfsmount *mounted = lookup_mnt(path);
if (!mounted)
break;
- dput(*dentry);
- mntput(*mnt);
- *mnt = mounted;
- *dentry = dget(mounted->mnt_root);
+ dput(path->dentry);
+ mntput(path->mnt);
+ path->mnt = mounted;
+ path->dentry = dget(mounted->mnt_root);
}
}
/* no need for dcache_lock, as serialization is taken care in
* namespace.c
*/
-int follow_down(struct vfsmount **mnt, struct dentry **dentry)
+int follow_down(struct path *path)
{
struct vfsmount *mounted;
- mounted = lookup_mnt(*mnt, *dentry);
+ mounted = lookup_mnt(path);
if (mounted) {
- dput(*dentry);
- mntput(*mnt);
- *mnt = mounted;
- *dentry = dget(mounted->mnt_root);
+ dput(path->dentry);
+ mntput(path->mnt);
+ path->mnt = mounted;
+ path->dentry = dget(mounted->mnt_root);
return 1;
}
return 0;
static __always_inline void follow_dotdot(struct nameidata *nd)
{
- struct fs_struct *fs = current->fs;
+ set_root(nd);
while(1) {
struct vfsmount *parent;
struct dentry *old = nd->path.dentry;
- read_lock(&fs->lock);
- if (nd->path.dentry == fs->root.dentry &&
- nd->path.mnt == fs->root.mnt) {
- read_unlock(&fs->lock);
+ if (nd->path.dentry == nd->root.dentry &&
+ nd->path.mnt == nd->root.mnt) {
break;
}
- read_unlock(&fs->lock);
spin_lock(&dcache_lock);
if (nd->path.dentry != nd->path.mnt->mnt_root) {
nd->path.dentry = dget(nd->path.dentry->d_parent);
mntput(nd->path.mnt);
nd->path.mnt = parent;
}
- follow_mount(&nd->path.mnt, &nd->path.dentry);
+ follow_mount(&nd->path);
}
/*
nd->flags |= LOOKUP_CONTINUE;
err = exec_permission_lite(inode);
- if (err == -EAGAIN)
- err = inode_permission(nd->path.dentry->d_inode,
- MAY_EXEC);
if (err)
break;
return link_path_walk(name, nd);
}
-/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
-static int do_path_lookup(int dfd, const char *name,
- unsigned int flags, struct nameidata *nd)
+static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd)
{
int retval = 0;
int fput_needed;
struct file *file;
- struct fs_struct *fs = current->fs;
nd->last_type = LAST_ROOT; /* if there are only slashes... */
nd->flags = flags;
nd->depth = 0;
+ nd->root.mnt = NULL;
if (*name=='/') {
- read_lock(&fs->lock);
- nd->path = fs->root;
- path_get(&fs->root);
- read_unlock(&fs->lock);
+ set_root(nd);
+ nd->path = nd->root;
+ path_get(&nd->root);
} else if (dfd == AT_FDCWD) {
+ struct fs_struct *fs = current->fs;
read_lock(&fs->lock);
nd->path = fs->pwd;
path_get(&fs->pwd);
fput_light(file, fput_needed);
}
+ return 0;
- retval = path_walk(name, nd);
+fput_fail:
+ fput_light(file, fput_needed);
+out_fail:
+ return retval;
+}
+
+/* Returns 0 and nd will be valid on success; Retuns error, otherwise. */
+static int do_path_lookup(int dfd, const char *name,
+ unsigned int flags, struct nameidata *nd)
+{
+ int retval = path_init(dfd, name, flags, nd);
+ if (!retval)
+ retval = path_walk(name, nd);
if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&
nd->path.dentry->d_inode))
audit_inode(name, nd->path.dentry);
-out_fail:
+ if (nd->root.mnt) {
+ path_put(&nd->root);
+ nd->root.mnt = NULL;
+ }
return retval;
-
-fput_fail:
- fput_light(file, fput_needed);
- goto out_fail;
}
int path_lookup(const char *name, unsigned int flags,
nd->path.dentry = dentry;
nd->path.mnt = mnt;
path_get(&nd->path);
+ nd->root = nd->path;
+ path_get(&nd->root);
retval = path_walk(name, nd);
if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&
nd->path.dentry->d_inode))
audit_inode(name, nd->path.dentry);
- return retval;
+ path_put(&nd->root);
+ nd->root.mnt = NULL;
+ return retval;
}
/**
* @nd: pointer to nameidata
* @open_flags: open intent flags
*/
-int path_lookup_open(int dfd, const char *name, unsigned int lookup_flags,
- struct nameidata *nd, int open_flags)
+static int path_lookup_open(int dfd, const char *name,
+ unsigned int lookup_flags, struct nameidata *nd, int open_flags)
{
struct file *filp = get_empty_filp();
int err;
int err;
struct qstr this;
+ WARN_ON_ONCE(!mutex_is_locked(&base->d_inode->i_mutex));
+
err = __lookup_one_len(name, &this, base, len);
if (err)
return ERR_PTR(err);
if (!inode)
return -ENOENT;
- if (S_ISLNK(inode->i_mode))
+ switch (inode->i_mode & S_IFMT) {
+ case S_IFLNK:
return -ELOOP;
-
- if (S_ISDIR(inode->i_mode) && (acc_mode & MAY_WRITE))
- return -EISDIR;
-
- /*
- * FIFO's, sockets and device files are special: they don't
- * actually live on the filesystem itself, and as such you
- * can write to them even if the filesystem is read-only.
- */
- if (S_ISFIFO(inode->i_mode) || S_ISSOCK(inode->i_mode)) {
- flag &= ~O_TRUNC;
- } else if (S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode)) {
+ case S_IFDIR:
+ if (acc_mode & MAY_WRITE)
+ return -EISDIR;
+ break;
+ case S_IFBLK:
+ case S_IFCHR:
if (path->mnt->mnt_flags & MNT_NODEV)
return -EACCES;
-
+ /*FALLTHRU*/
+ case S_IFIFO:
+ case S_IFSOCK:
flag &= ~O_TRUNC;
+ break;
}
error = inode_permission(inode, acc_mode);
if (error)
return error;
+
+ error = ima_path_check(path, acc_mode ?
+ acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC) :
+ ACC_MODE(flag) & (MAY_READ | MAY_WRITE),
+ IMA_COUNT_UPDATE);
+
+ if (error)
+ return error;
/*
* An append-only file must be opened in append mode for writing.
*/
if (IS_APPEND(inode)) {
+ error = -EPERM;
if ((flag & FMODE_WRITE) && !(flag & O_APPEND))
- return -EPERM;
+ goto err_out;
if (flag & O_TRUNC)
- return -EPERM;
+ goto err_out;
}
/* O_NOATIME can only be set by the owner or superuser */
if (flag & O_NOATIME)
- if (!is_owner_or_cap(inode))
- return -EPERM;
+ if (!is_owner_or_cap(inode)) {
+ error = -EPERM;
+ goto err_out;
+ }
/*
* Ensure there are no outstanding leases on the file.
*/
error = break_lease(inode, flag);
if (error)
- return error;
+ goto err_out;
if (flag & O_TRUNC) {
error = get_write_access(inode);
if (error)
- return error;
+ goto err_out;
/*
* Refuse to truncate files with mandatory locks held on them.
}
put_write_access(inode);
if (error)
- return error;
+ goto err_out;
} else
if (flag & FMODE_WRITE)
vfs_dq_init(inode);
return 0;
+err_out:
+ ima_counts_put(path, acc_mode ?
+ acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC) :
+ ACC_MODE(flag) & (MAY_READ | MAY_WRITE));
+ return error;
}
/*
struct dentry *dir = nd->path.dentry;
if (!IS_POSIXACL(dir->d_inode))
- mode &= ~current->fs->umask;
+ mode &= ~current_umask();
error = security_path_mknod(&nd->path, path->dentry, mode, 0);
if (error)
goto out_unlock;
* open_to_namei_flags() for more details.
*/
struct file *do_filp_open(int dfd, const char *pathname,
- int open_flag, int mode)
+ int open_flag, int mode, int acc_mode)
{
struct file *filp;
struct nameidata nd;
- int acc_mode, error;
+ int error;
struct path path;
struct dentry *dir;
int count = 0;
int will_write;
int flag = open_to_namei_flags(open_flag);
- acc_mode = MAY_OPEN | ACC_MODE(flag);
+ if (!acc_mode)
+ acc_mode = MAY_OPEN | ACC_MODE(flag);
/* O_TRUNC implies we need access checks for write permissions */
if (flag & O_TRUNC)
/*
* Create - we need to know the parent.
*/
- error = do_path_lookup(dfd, pathname, LOOKUP_PARENT, &nd);
+ error = path_init(dfd, pathname, LOOKUP_PARENT, &nd);
if (error)
return ERR_PTR(error);
+ error = path_walk(pathname, &nd);
+ if (error) {
+ if (nd.root.mnt)
+ path_put(&nd.root);
+ return ERR_PTR(error);
+ }
+ if (unlikely(!audit_dummy_context()))
+ audit_inode(pathname, nd.path.dentry);
/*
* We have the parent and last component. First of all, check
goto exit;
}
filp = nameidata_to_filp(&nd, open_flag);
+ if (IS_ERR(filp))
+ ima_counts_put(&nd.path,
+ acc_mode & (MAY_READ | MAY_WRITE |
+ MAY_EXEC));
mnt_drop_write(nd.path.mnt);
+ if (nd.root.mnt)
+ path_put(&nd.root);
return filp;
}
goto exit;
}
filp = nameidata_to_filp(&nd, open_flag);
+ if (IS_ERR(filp))
+ ima_counts_put(&nd.path,
+ acc_mode & (MAY_READ | MAY_WRITE | MAY_EXEC));
/*
* It is now safe to drop the mnt write
* because the filp has had a write taken
*/
if (will_write)
mnt_drop_write(nd.path.mnt);
+ if (nd.root.mnt)
+ path_put(&nd.root);
return filp;
exit_mutex_unlock:
if (!IS_ERR(nd.intent.open.file))
release_open_intent(&nd);
exit_parent:
+ if (nd.root.mnt)
+ path_put(&nd.root);
path_put(&nd.path);
return ERR_PTR(error);
* with "intent.open".
*/
release_open_intent(&nd);
+ if (nd.root.mnt)
+ path_put(&nd.root);
return ERR_PTR(error);
}
nd.flags &= ~LOOKUP_PARENT;
*/
struct file *filp_open(const char *filename, int flags, int mode)
{
- return do_filp_open(AT_FDCWD, filename, flags, mode);
+ return do_filp_open(AT_FDCWD, filename, flags, mode, 0);
}
EXPORT_SYMBOL(filp_open);
goto out_unlock;
}
if (!IS_POSIXACL(nd.path.dentry->d_inode))
- mode &= ~current->fs->umask;
+ mode &= ~current_umask();
error = may_mknod(mode);
if (error)
goto out_dput;
goto out_unlock;
if (!IS_POSIXACL(nd.path.dentry->d_inode))
- mode &= ~current->fs->umask;
+ mode &= ~current_umask();
error = mnt_want_write(nd.path.mnt);
if (error)
goto out_dput;
EXPORT_SYMBOL(vfs_unlink);
EXPORT_SYMBOL(dentry_unhash);
EXPORT_SYMBOL(generic_readlink);
-
-/* to be mentioned only in INIT_TASK */
-struct fs_struct init_fs = {
- .count = ATOMIC_INIT(1),
- .lock = __RW_LOCK_UNLOCKED(init_fs.lock),
- .umask = 0022,
-};