222 lines
6.6 KiB
Diff
222 lines
6.6 KiB
Diff
From: Andreas Gruenbacher <agruen@suse.de>
|
|
Date: Fri, 11 Jun 2010 16:12:44 +0530
|
|
Subject: [PATCH 01/16] vfs: Hooks for more fine-grained directory permission checking
|
|
Patch-mainline: not yet
|
|
|
|
Add iop->may_create and iop->may_delete for overriding the POSIX file
|
|
permission checks when creating and deleting files. File systems can
|
|
implement these hooks to support permission models which use different
|
|
rules for file creation and deletion.
|
|
|
|
When these hooks are not used, the vfs behavior remains unchanged.
|
|
|
|
Signed-off-by: Andreas Gruenbacher <agruen@suse.de>
|
|
Signed-off-by: Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com>
|
|
---
|
|
fs/namei.c | 82 ++++++++++++++++++++++++++++++++++++++++++-----------
|
|
include/linux/fs.h | 4 ++
|
|
2 files changed, 69 insertions(+), 17 deletions(-)
|
|
|
|
--- a/fs/namei.c
|
|
+++ b/fs/namei.c
|
|
@@ -1283,6 +1283,26 @@ static inline int check_sticky(struct in
|
|
}
|
|
|
|
/*
|
|
+ * Do the directory specific tests of inode_permission() and call the
|
|
+ * may_delete inode operation. The may_delete inode operation must do the
|
|
+ * sticky check when needed.
|
|
+ */
|
|
+static int may_delete_iop(struct inode *dir, struct inode *inode, int replace)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ if (IS_RDONLY(dir))
|
|
+ return -EROFS;
|
|
+ if (IS_IMMUTABLE(dir))
|
|
+ return -EACCES;
|
|
+ error = dir->i_op->may_delete(dir, inode, replace);
|
|
+ if (!error)
|
|
+ error = security_inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
+/*
|
|
* Check whether we can remove a link victim from directory dir, check
|
|
* whether the type of victim is right.
|
|
* 1. We can't do it if dir is read-only (done in permission())
|
|
@@ -1301,7 +1321,8 @@ static inline int check_sticky(struct in
|
|
* 10. We don't allow removal of NFS sillyrenamed files; it's handled by
|
|
* nfs_async_unlink().
|
|
*/
|
|
-static int may_delete(struct inode *dir,struct dentry *victim,int isdir)
|
|
+static int may_delete(struct inode *dir, struct dentry *victim,
|
|
+ int isdir, int replace)
|
|
{
|
|
int error;
|
|
|
|
@@ -1310,14 +1331,19 @@ static int may_delete(struct inode *dir,
|
|
|
|
BUG_ON(victim->d_parent->d_inode != dir);
|
|
audit_inode_child(victim, dir);
|
|
-
|
|
- error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
|
+ if (dir->i_op->may_delete)
|
|
+ error = may_delete_iop(dir, victim->d_inode, replace);
|
|
+ else {
|
|
+ error = inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
|
+ if (!error && check_sticky(dir, victim->d_inode))
|
|
+ error = -EPERM;
|
|
+ }
|
|
if (error)
|
|
return error;
|
|
if (IS_APPEND(dir))
|
|
return -EPERM;
|
|
- if (check_sticky(dir, victim->d_inode)||IS_APPEND(victim->d_inode)||
|
|
- IS_IMMUTABLE(victim->d_inode) || IS_SWAPFILE(victim->d_inode))
|
|
+ if (IS_APPEND(victim->d_inode) || IS_IMMUTABLE(victim->d_inode) ||
|
|
+ IS_SWAPFILE(victim->d_inode))
|
|
return -EPERM;
|
|
if (isdir) {
|
|
if (!S_ISDIR(victim->d_inode->i_mode))
|
|
@@ -1333,6 +1359,25 @@ static int may_delete(struct inode *dir,
|
|
return 0;
|
|
}
|
|
|
|
+/*
|
|
+ * Do the directory specific tests of inode_permission() and call the
|
|
+ * may_create inode operation.
|
|
+ */
|
|
+static int may_create_iop(struct inode *dir, int isdir)
|
|
+{
|
|
+ int error;
|
|
+
|
|
+ if (IS_RDONLY(dir))
|
|
+ return -EROFS;
|
|
+ if (IS_IMMUTABLE(dir))
|
|
+ return -EACCES;
|
|
+ error = dir->i_op->may_create(dir, isdir);
|
|
+ if (!error)
|
|
+ error = security_inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
|
+
|
|
+ return error;
|
|
+}
|
|
+
|
|
/* Check whether we can create an object with dentry child in directory
|
|
* dir.
|
|
* 1. We can't do it if child already exists (open has special treatment for
|
|
@@ -1341,13 +1386,16 @@ static int may_delete(struct inode *dir,
|
|
* 3. We should have write and exec permissions on dir
|
|
* 4. We can't do it if dir is immutable (done in permission())
|
|
*/
|
|
-static inline int may_create(struct inode *dir, struct dentry *child)
|
|
+static inline int may_create(struct inode *dir, struct dentry *child, int isdir)
|
|
{
|
|
if (child->d_inode)
|
|
return -EEXIST;
|
|
if (IS_DEADDIR(dir))
|
|
return -ENOENT;
|
|
- return inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
|
+ if (dir->i_op->may_create)
|
|
+ return may_create_iop(dir, isdir);
|
|
+ else
|
|
+ return inode_permission(dir, MAY_WRITE | MAY_EXEC);
|
|
}
|
|
|
|
/*
|
|
@@ -1395,7 +1443,7 @@ void unlock_rename(struct dentry *p1, st
|
|
int vfs_create(struct inode *dir, struct dentry *dentry, int mode,
|
|
struct nameidata *nd)
|
|
{
|
|
- int error = may_create(dir, dentry);
|
|
+ int error = may_create(dir, dentry, 0);
|
|
|
|
if (error)
|
|
return error;
|
|
@@ -1957,7 +2005,7 @@ EXPORT_SYMBOL_GPL(lookup_create);
|
|
|
|
int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)
|
|
{
|
|
- int error = may_create(dir, dentry);
|
|
+ int error = may_create(dir, dentry, 0);
|
|
|
|
if (error)
|
|
return error;
|
|
@@ -2061,7 +2109,7 @@ SYSCALL_DEFINE3(mknod, const char __user
|
|
|
|
int vfs_mkdir(struct inode *dir, struct dentry *dentry, int mode)
|
|
{
|
|
- int error = may_create(dir, dentry);
|
|
+ int error = may_create(dir, dentry, 1);
|
|
|
|
if (error)
|
|
return error;
|
|
@@ -2151,7 +2199,7 @@ void dentry_unhash(struct dentry *dentry
|
|
|
|
int vfs_rmdir(struct inode *dir, struct dentry *dentry)
|
|
{
|
|
- int error = may_delete(dir, dentry, 1);
|
|
+ int error = may_delete(dir, dentry, 1, 0);
|
|
|
|
if (error)
|
|
return error;
|
|
@@ -2238,7 +2286,7 @@ SYSCALL_DEFINE1(rmdir, const char __user
|
|
|
|
int vfs_unlink(struct inode *dir, struct dentry *dentry)
|
|
{
|
|
- int error = may_delete(dir, dentry, 0);
|
|
+ int error = may_delete(dir, dentry, 0, 0);
|
|
|
|
if (error)
|
|
return error;
|
|
@@ -2346,7 +2394,7 @@ SYSCALL_DEFINE1(unlink, const char __use
|
|
|
|
int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname)
|
|
{
|
|
- int error = may_create(dir, dentry);
|
|
+ int error = may_create(dir, dentry, 0);
|
|
|
|
if (error)
|
|
return error;
|
|
@@ -2419,7 +2467,7 @@ int vfs_link(struct dentry *old_dentry,
|
|
if (!inode)
|
|
return -ENOENT;
|
|
|
|
- error = may_create(dir, new_dentry);
|
|
+ error = may_create(dir, new_dentry, S_ISDIR(inode->i_mode));
|
|
if (error)
|
|
return error;
|
|
|
|
@@ -2630,14 +2678,14 @@ int vfs_rename(struct inode *old_dir, st
|
|
if (old_dentry->d_inode == new_dentry->d_inode)
|
|
return 0;
|
|
|
|
- error = may_delete(old_dir, old_dentry, is_dir);
|
|
+ error = may_delete(old_dir, old_dentry, is_dir, 0);
|
|
if (error)
|
|
return error;
|
|
|
|
if (!new_dentry->d_inode)
|
|
- error = may_create(new_dir, new_dentry);
|
|
+ error = may_create(new_dir, new_dentry, is_dir);
|
|
else
|
|
- error = may_delete(new_dir, new_dentry, is_dir);
|
|
+ error = may_delete(new_dir, new_dentry, is_dir, 1);
|
|
if (error)
|
|
return error;
|
|
|
|
--- a/include/linux/fs.h
|
|
+++ b/include/linux/fs.h
|
|
@@ -1542,6 +1542,10 @@ struct inode_operations {
|
|
void (*truncate_range)(struct inode *, loff_t, loff_t);
|
|
int (*fiemap)(struct inode *, struct fiemap_extent_info *, u64 start,
|
|
u64 len);
|
|
+ int (*may_create) (struct inode *, int);
|
|
+ int (*may_delete) (struct inode *, struct inode *, int);
|
|
+
|
|
+
|
|
} ____cacheline_aligned;
|
|
|
|
struct seq_file;
|