/*
* This does basic POSIX ACL permission checking
*/
-static inline int __acl_permission_check(struct inode *inode, int mask,
- int (*check_acl)(struct inode *inode, int mask), int rcu)
+static int acl_permission_check(struct inode *inode, int mask, unsigned int flags,
+ int (*check_acl)(struct inode *inode, int mask, unsigned int flags))
{
umode_t mode = inode->i_mode;
mode >>= 6;
else {
if (IS_POSIXACL(inode) && (mode & S_IRWXG) && check_acl) {
- if (rcu) {
- return -ECHILD;
- } else {
- int error = check_acl(inode, mask);
- if (error != -EAGAIN)
- return error;
- }
+ int error = check_acl(inode, mask, flags);
+ if (error != -EAGAIN)
+ return error;
}
if (in_group_p(inode->i_gid))
return -EACCES;
}
-static inline int acl_permission_check(struct inode *inode, int mask,
- int (*check_acl)(struct inode *inode, int mask))
-{
- return __acl_permission_check(inode, mask, check_acl, 0);
-}
-
/**
- * generic_permission - check for access rights on a Posix-like filesystem
+ * 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
+ * @flags: IPERM_FLAG_ flags.
*
* 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..
+ * are used for other things.
+ *
+ * generic_permission is rcu-walk aware. It returns -ECHILD in case an rcu-walk
+ * request cannot be satisfied (eg. requires blocking or too much complexity).
+ * It would then be called again in ref-walk mode.
*/
-int generic_permission(struct inode *inode, int mask,
- int (*check_acl)(struct inode *inode, int mask))
+int generic_permission(struct inode *inode, int mask, unsigned int flags,
+ int (*check_acl)(struct inode *inode, int mask, unsigned int flags))
{
int ret;
/*
* Do the basic POSIX ACL permission checks.
*/
- ret = acl_permission_check(inode, mask, check_acl);
+ ret = acl_permission_check(inode, mask, flags, check_acl);
if (ret != -EACCES)
return ret;
}
if (inode->i_op->permission)
- retval = inode->i_op->permission(inode, mask);
+ retval = inode->i_op->permission(inode, mask, 0);
else
- retval = generic_permission(inode, mask, inode->i_op->check_acl);
+ retval = generic_permission(inode, mask, 0,
+ inode->i_op->check_acl);
if (retval)
return retval;
}
EXPORT_SYMBOL(path_get);
+/**
+ * path_get_long - get a long reference to a path
+ * @path: path to get the reference to
+ *
+ * Given a path increment the reference count to the dentry and the vfsmount.
+ */
+void path_get_long(struct path *path)
+{
+ mntget_long(path->mnt);
+ dget(path->dentry);
+}
+
/**
* path_put - put a reference to a path
* @path: path to put the reference to
}
EXPORT_SYMBOL(path_put);
+/**
+ * path_put_long - put a long reference to a path
+ * @path: path to put the reference to
+ *
+ * Given a path decrement the reference count to the dentry and the vfsmount.
+ */
+void path_put_long(struct path *path)
+{
+ dput(path->dentry);
+ mntput_long(path->mnt);
+}
+
/**
* nameidata_drop_rcu - drop this nameidata out of rcu-walk
* @nd: nameidata pathwalk data to drop
- * @Returns: 0 on success, -ECHLID on failure
+ * Returns: 0 on success, -ECHILD on failure
*
* Path walking has 2 modes, rcu-walk and ref-walk (see
* Documentation/filesystems/path-lookup.txt). __drop_rcu* functions attempt
* nameidata_dentry_drop_rcu - drop nameidata and dentry out of rcu-walk
* @nd: nameidata pathwalk data to drop
* @dentry: dentry to drop
- * @Returns: 0 on success, -ECHLID on failure
+ * Returns: 0 on success, -ECHILD on failure
*
* nameidata_dentry_drop_rcu attempts to drop the current nd->path and nd->root,
* and dentry into ref-walk. @dentry must be a path found by a do_lookup call on
/**
* nameidata_drop_rcu_last - drop nameidata ending path walk out of rcu-walk
* @nd: nameidata pathwalk data to drop
- * @Returns: 0 on success, -ECHLID on failure
+ * Returns: 0 on success, -ECHILD on failure
*
* nameidata_drop_rcu_last attempts to drop the current nd->path into ref-walk.
* nd->path should be the final element of the lookup, so nd->root is discarded.
fput(nd->intent.open.file);
}
+static int d_revalidate(struct dentry *dentry, struct nameidata *nd)
+{
+ int status;
+
+ status = dentry->d_op->d_revalidate(dentry, nd);
+ if (status == -ECHILD) {
+ if (nameidata_dentry_drop_rcu(nd, dentry))
+ return status;
+ status = dentry->d_op->d_revalidate(dentry, nd);
+ }
+
+ return status;
+}
+
static inline struct dentry *
do_revalidate(struct dentry *dentry, struct nameidata *nd)
{
- int status = dentry->d_op->d_revalidate(dentry, nd);
+ int status;
+
+ status = d_revalidate(dentry, nd);
if (unlikely(status <= 0)) {
/*
* The dentry failed validation.
* the dentry otherwise d_revalidate is asking us
* to return a fail status.
*/
- if (!status) {
+ if (status < 0) {
+ /* If we're in rcu-walk, we don't have a ref */
+ if (!(nd->flags & LOOKUP_RCU))
+ dput(dentry);
+ dentry = ERR_PTR(status);
+
+ } else {
+ /* Don't d_invalidate in rcu-walk mode */
+ if (nameidata_dentry_drop_rcu_maybe(nd, dentry))
+ return ERR_PTR(-ECHILD);
if (!d_invalidate(dentry)) {
dput(dentry);
dentry = NULL;
}
- } else {
- dput(dentry);
- dentry = ERR_PTR(status);
}
}
return dentry;
if (!need_reval_dot(dentry))
return 0;
- status = dentry->d_op->d_revalidate(dentry, nd);
+ status = d_revalidate(dentry, nd);
if (status > 0)
return 0;
* short-cut DAC fails, then call ->permission() to do more
* complete permission check.
*/
-static inline int __exec_permission(struct inode *inode, int rcu)
+static inline int exec_permission(struct inode *inode, unsigned int flags)
{
int ret;
if (inode->i_op->permission) {
- if (rcu)
- return -ECHILD;
- ret = inode->i_op->permission(inode, MAY_EXEC);
- if (!ret)
- goto ok;
- return ret;
+ ret = inode->i_op->permission(inode, MAY_EXEC, flags);
+ } else {
+ ret = acl_permission_check(inode, MAY_EXEC, flags,
+ inode->i_op->check_acl);
}
- ret = __acl_permission_check(inode, MAY_EXEC, inode->i_op->check_acl, rcu);
- if (!ret)
+ if (likely(!ret))
goto ok;
- if (rcu && ret == -ECHILD)
+ if (ret == -ECHILD)
return ret;
if (capable(CAP_DAC_OVERRIDE) || capable(CAP_DAC_READ_SEARCH))
return ret;
ok:
- return security_inode_exec_permission(inode, rcu);
-}
-
-static int exec_permission(struct inode *inode)
-{
- return __exec_permission(inode, 0);
-}
-
-static int exec_permission_rcu(struct inode *inode)
-{
- return __exec_permission(inode, 1);
+ return security_inode_exec_permission(inode, flags);
}
static __always_inline void set_root(struct nameidata *nd)
return -ECHILD;
nd->seq = seq;
- if (dentry->d_flags & DCACHE_OP_REVALIDATE) {
- /* We commonly drop rcu-walk here */
- if (nameidata_dentry_drop_rcu(nd, dentry))
- return -ECHILD;
+ if (dentry->d_flags & DCACHE_OP_REVALIDATE)
goto need_revalidate;
- }
path->mnt = mnt;
path->dentry = dentry;
__follow_mount_rcu(nd, path, inode);
nd->flags |= LOOKUP_CONTINUE;
if (nd->flags & LOOKUP_RCU) {
- err = exec_permission_rcu(nd->inode);
+ err = exec_permission(nd->inode, IPERM_FLAG_RCU);
if (err == -ECHILD) {
if (nameidata_drop_rcu(nd))
return -ECHILD;
}
} else {
exec_again:
- err = exec_permission(nd->inode);
+ err = exec_permission(nd->inode, 0);
}
if (err)
break;
* We may need to check the cached dentry for staleness.
*/
if (need_reval_dot(nd->path.dentry)) {
- if (nameidata_drop_rcu_maybe(nd))
- return -ECHILD;
- err = -ESTALE;
/* Note: we do not d_invalidate() */
- if (!nd->path.dentry->d_op->d_revalidate(
- nd->path.dentry, nd))
+ err = d_revalidate(nd->path.dentry, nd);
+ if (!err)
+ err = -ESTALE;
+ if (err < 0)
break;
}
return_base:
struct dentry *dentry;
int err;
- err = exec_permission(inode);
+ err = exec_permission(inode, 0);
if (err)
return ERR_PTR(err);
dir = nd->path.dentry;
case LAST_DOT:
if (need_reval_dot(dir)) {
- if (!dir->d_op->d_revalidate(dir, nd)) {
+ error = d_revalidate(nd->path.dentry, nd);
+ if (!error)
error = -ESTALE;
+ if (error < 0)
goto exit;
- }
}
/* fallthrough */
case LAST_ROOT: