SELinux metadata protection

From: KaiGai Kohei (kaigai@private)
Date: Sat Dec 31 2005 - 10:11:47 PST


Hello,

Nowaday, I'm considering about a philosophical theme.

In my understanding, file-metadata includes _filename_ similar to filesize,
update-timestamp, and so on. Most of permissions for reading metadata are
defined as 'getattr' in SELinux.
But permission for reading filename is defined as 'read' of parent directory,
'getattr' of each child-entries are not evaluated.

It seems a bit curious behavior for me. Why can an unauthorized process
be allowed to know whether the file exists or not ?
I think it's worthwhile to conceal the existence of files from unauthorized
processes.

This patch enables to conceal the unauthorized files.
[1/2] LSM_metadata_protection_v1.patch
 - add security_file_readdir(), it's called when readdir() write each filename
   to userspace.
 - add security_inode_lookup(), it's called when filename is resolved by path_walk().

[2/2] SELinux_metadata_protection_v1.patch
 - add checking 'getattr' permission on file_readdirand inode_lookup hooks.

Thanks, any comments please, and have a good year :)

---- without Metadata Protection patch --------
[root@saba test]# uname -r
2.6.14-1.1653_FC4
[root@saba test]# id -Z
root:system_r:unconfined_t
[root@saba test]# ls -lZ
drwxr-xr-x  root     root     root:object_r:tmp_t              dir1
drwxr-xr-x  root     root     root:object_r:hidden_file_t      dir2
-rw-r--r--  root     root     root:object_r:tmp_t              file1
-rw-r--r--  root     root     root:object_r:hidden_file_t      file2
-rw-r--r--  root     root     root:object_r:hidden_file_t      file3
-rw-r--r--  root     root     root:object_r:tmp_t              file4
[root@saba test]# setenforce 1
[root@saba test]# ls -lZ               <-- readdir() contains unauthorized filenames.
drwxr-xr-x  root     root     root:object_r:tmp_t              dir1
?---------  ?        ?                                         dir2
-rw-r--r--  root     root     root:object_r:tmp_t              file1
?---------  ?        ?                                         file2   <-- getattr is not allowed
?---------  ?        ?                                         file3
-rw-r--r--  root     root     root:object_r:tmp_t              file4
[root@saba test]# cat file2
cat: file2: Permission denied          <-- process can know "file2 exists!".
[root@saba test]#

---- with Metadata Protection patch --------
[root@saba test]# uname -r
2.6.14.5-selinux.mp
[root@saba test]# id -Z
root:system_r:unconfined_t
[root@saba test]# ls -lZ
drwxr-xr-x  root     root     root:object_r:tmp_t              dir1
drwxr-xr-x  root     root     root:object_r:hidden_file_t      dir2
-rw-r--r--  root     root     root:object_r:tmp_t              file1
-rw-r--r--  root     root     root:object_r:hidden_file_t      file2
-rw-r--r--  root     root     root:object_r:hidden_file_t      file3
-rw-r--r--  root     root     root:object_r:tmp_t              file4
[root@saba test]# setenforce 1
[root@saba test]# ls -lZ               <-- readdir() doesn't contain unauthorized filenames.
drwxr-xr-x  root     root     root:object_r:tmp_t              dir1
-rw-r--r--  root     root     root:object_r:tmp_t              file1
-rw-r--r--  root     root     root:object_r:tmp_t              file4
[root@saba test]# cat file2
cat: file2: No such file or directory  <-- process can not know whether file2 exists.
[root@saba test]#

-- 
KaiGai Kohei <kaigai@private>


--- linux-2.6.14.5-selinux/security/selinux/hooks.c	2005-12-31 05:38:32.000000000 -0500
+++ linux-2.6.14.5-selinux.mp/security/selinux/hooks.c	2005-12-31 05:11:12.000000000 -0500
@@ -2009,6 +2009,11 @@ static int selinux_inode_init_security(s
 	return 0;
 }
 
+static int selinux_inode_lookup(struct vfsmount *mnt, struct dentry *dentry)
+{
+	return dentry_has_perm(current, mnt, dentry, FILE__GETATTR) ? -ENOENT : 0;
+}
+
 static int selinux_inode_create(struct inode *dir, struct dentry *dentry, int mask)
 {
 	return may_create(dir, dentry, SECCLASS_FILE);
@@ -2336,6 +2341,28 @@ static int selinux_file_permission(struc
 			     file_mask_to_av(inode->i_mode, mask));
 }
 
+static int selinux_file_readdir(struct file *dir, struct qstr *qname)
+{
+	struct dentry *dentry;
+	int rc = -ENOENT;
+
+	if (qname->len==1 && qname->name[0]=='.') {
+		dentry = dget(dir->f_dentry);
+	} else if (qname->len==2 && qname->name[0]=='.' && qname->name[1]=='.') {
+		dentry = dget(dir->f_dentry->d_parent);
+	} else {
+		dentry = lookup_hash(qname, dir->f_dentry);
+	}
+	if (!IS_ERR(dentry)) {
+		if (dentry->d_inode)
+			rc = dentry_has_perm(current, dir->f_vfsmnt, dentry, FILE__GETATTR);
+		dput(dentry);
+	} else {
+		rc = PTR_ERR(dentry);
+	}
+	return selinux_enforcing ? rc : 0;
+}
+
 static int selinux_file_alloc_security(struct file *file)
 {
 	return file_alloc_security(file);
@@ -4274,6 +4301,7 @@ static struct security_operations selinu
 	.inode_alloc_security =		selinux_inode_alloc_security,
 	.inode_free_security =		selinux_inode_free_security,
 	.inode_init_security =		selinux_inode_init_security,
+	.inode_lookup =			selinux_inode_lookup,
 	.inode_create =			selinux_inode_create,
 	.inode_link =			selinux_inode_link,
 	.inode_unlink =			selinux_inode_unlink,
@@ -4297,6 +4325,7 @@ static struct security_operations selinu
 	.inode_listsecurity =           selinux_inode_listsecurity,
 
 	.file_permission =		selinux_file_permission,
+	.file_readdir =			selinux_file_readdir,
 	.file_alloc_security =		selinux_file_alloc_security,
 	.file_free_security =		selinux_file_free_security,
 	.file_ioctl =			selinux_file_ioctl,

--- linux-2.6.14.5-selinux/include/linux/security.h	2005-12-31 05:38:32.000000000 -0500
+++ linux-2.6.14.5-selinux.mp/include/linux/security.h	2005-12-31 05:35:26.000000000 -0500
@@ -269,6 +269,11 @@ struct swap_info_struct;
  *	Returns 0 if @name and @value have been successfully set,
  *		-EOPNOTSUPP if no security attribute is needed, or
  *		-ENOMEM on memory allocation failure.
+ * @ inode_lookup
+ *	Check permission before resolving a filename.
+ *	@mnt is the vfsmount where the dentry was looked up
+ *	@dentry contains the dentry structure for the file.
+ *	Return 0 if permission is granted.
  * @inode_create:
  *	Check permission to create a regular file.
  *	@dir contains inode structure of the parent of the new file.
@@ -422,6 +427,13 @@ struct swap_info_struct;
  *	@file contains the file structure being accessed.
  *	@mask contains the requested permissions.
  *	Return 0 if permission is granted.
+ * @file_readdir
+ *	Check permission while an readdir operation.
+ *	This hook is called for each child entry. If reading filename of
+ *	the child entry is not permitted, a filldir operation will skipped.
+ *	@dir contains the file structure of the parent directory.
+ *	@qname contains the qstr structure of the name of child entry.
+ *	Return 0 if permission is granted.
  * @file_alloc_security:
  *	Allocate and attach a security structure to the file->f_security field.
  *	The security field is initialized to NULL when the structure is first
@@ -1068,6 +1080,7 @@ struct security_operations {
 	void (*inode_free_security) (struct inode *inode);
 	int (*inode_init_security) (struct inode *inode, struct inode *dir,
 				    char **name, void **value, size_t *len);
+	int (*inode_lookup) (struct vfsmount *mnt, struct dentry *dentry);
 	int (*inode_create) (struct inode *dir,
 	                     struct dentry *dentry, int mode);
 	int (*inode_link) (struct dentry *old_dentry,
@@ -1099,6 +1112,7 @@ struct security_operations {
   	int (*inode_listsecurity)(struct inode *inode, char *buffer, size_t buffer_size);
 
 	int (*file_permission) (struct file * file, int mask);
+	int (*file_readdir) (struct file * dir, struct qstr *qname);
 	int (*file_alloc_security) (struct file * file);
 	void (*file_free_security) (struct file * file);
 	int (*file_ioctl) (struct file * file, unsigned int cmd,
@@ -1426,7 +1440,15 @@ static inline int security_inode_init_se
 		return -EOPNOTSUPP;
 	return security_ops->inode_init_security (inode, dir, name, value, len);
 }
-	
+
+static inline int security_inode_lookup (struct vfsmount *mnt,
+					 struct dentry *dentry)
+{
+	if (unlikely (IS_PRIVATE (dentry->d_inode)))
+		return 0;
+	return security_ops->inode_lookup(mnt, dentry);
+}
+
 static inline int security_inode_create (struct inode *dir,
 					 struct dentry *dentry,
 					 int mode)
@@ -1609,6 +1631,25 @@ static inline int security_file_permissi
 	return security_ops->file_permission (file, mask);
 }
 
+extern int security_file_filldir (void *buffer, const char *name, int namelen,
+				  loff_t offset, ino_t ino, unsigned int d_type);
+typedef struct {
+	struct file *dir;
+	void *buffer;
+	filldir_t filler;
+} security_filldir_t;
+
+static inline int security_file_readdir (struct file *dir, void *buffer, filldir_t filler)
+{
+	security_filldir_t private;
+
+	private.dir = dir;
+	private.buffer = buffer;
+	private.filler = filler;
+
+	return dir->f_op->readdir (dir, &private, security_file_filldir);
+}
+
 static inline int security_file_alloc (struct file *file)
 {
 	return security_ops->file_alloc_security (file);
@@ -2112,7 +2153,13 @@ static inline int security_inode_init_se
 {
 	return -EOPNOTSUPP;
 }
-	
+
+static inline int security_inode_lookup (struct dentry *dentry,
+					 struct vfsmount *mnt)
+{
+	return 0;
+}
+
 static inline int security_inode_create (struct inode *dir,
 					 struct dentry *dentry,
 					 int mode)
@@ -2245,6 +2292,11 @@ static inline int security_file_permissi
 	return 0;
 }
 
+static inline int security_file_readdir (struct file *dir, void *buffer, filldir_t filldir)
+{
+	return dir->f_op->readdir(dir, buffer, filldir);
+}
+
 static inline int security_file_alloc (struct file *file)
 {
 	return 0;
--- linux-2.6.14.5-selinux/fs/readdir.c	2005-12-26 19:26:33.000000000 -0500
+++ linux-2.6.14.5-selinux.mp/fs/readdir.c	2005-12-29 20:26:55.000000000 -0500
@@ -33,7 +33,11 @@ int vfs_readdir(struct file *file, filld
 	down(&inode->i_sem);
 	res = -ENOENT;
 	if (!IS_DEADDIR(inode)) {
-		res = file->f_op->readdir(file, buf, filler);
+		/* NOTE:
+		   When LSM was not enable, security_file_readdir()
+		   is same as 'file->f_op->readdir()'. 
+		*/
+		res = security_file_readdir(file, buf, filler);
 		file_accessed(file);
 	}
 	up(&inode->i_sem);
--- linux-2.6.14.5-selinux/fs/namei.c	2005-12-26 19:26:33.000000000 -0500
+++ linux-2.6.14.5-selinux.mp/fs/namei.c	2005-12-31 05:10:04.000000000 -0500
@@ -812,6 +812,9 @@ static fastcall int __link_path_walk(con
 		inode = next.dentry->d_inode;
 		if (!inode)
 			goto out_dput;
+		err = security_inode_lookup(next.mnt, next.dentry);
+		if (err)
+			goto out_dput;
 		err = -ENOTDIR; 
 		if (!inode->i_op)
 			goto out_dput;
@@ -862,6 +865,11 @@ last_component:
 		if (err)
 			break;
 		inode = next.dentry->d_inode;
+		if (inode) {
+		err = security_inode_lookup(next.mnt, next.dentry);
+			if (err)
+				break;
+		}
 		if ((lookup_flags & LOOKUP_FOLLOW)
 		    && inode && inode->i_op && inode->i_op->follow_link) {
 			err = do_follow_link(&next, nd);
--- linux-2.6.14.5-selinux/security/security.c	2005-12-26 19:26:33.000000000 -0500
+++ linux-2.6.14.5-selinux.mp/security/security.c	2005-12-29 20:26:55.000000000 -0500
@@ -195,6 +195,23 @@ int capable(int cap)
 	return 1;
 }
 
+int security_file_filldir (void *buffer, const char *name, int namelen,
+			   loff_t offset, ino_t ino, unsigned int d_type)
+{
+	security_filldir_t *private = (security_filldir_t *)buffer;
+	struct file *dir = private->dir;
+	struct qstr qname;
+
+	qname.name = name;
+	qname.len = namelen;
+	qname.hash = full_name_hash(qname.name, qname.len);
+
+	if (!security_ops->file_readdir(dir, &qname))
+		return private->filler(private->buffer, name, namelen, offset, ino, d_type);
+
+	return 0;  /* skip filler if LSM denied. */
+}
+
 EXPORT_SYMBOL_GPL(register_security);
 EXPORT_SYMBOL_GPL(unregister_security);
 EXPORT_SYMBOL_GPL(mod_reg_security);
--- linux-2.6.14.5-selinux/security/dummy.c	2005-12-31 05:38:32.000000000 -0500
+++ linux-2.6.14.5-selinux.mp/security/dummy.c	2005-12-31 05:10:35.000000000 -0500
@@ -264,6 +264,11 @@ static int dummy_inode_init_security (st
 	return -EOPNOTSUPP;
 }
 
+static int dummy_inode_lookup (struct vfsmount *mnt, struct dentry *dentry)
+{
+	return 0;
+}
+
 static int dummy_inode_create (struct inode *inode, struct dentry *dentry,
 			       int mask)
 {
@@ -397,6 +402,11 @@ static int dummy_file_permission (struct
 	return 0;
 }
 
+static int dummy_file_readdir (struct file *dir, struct dentry *child)
+{
+	return 0;
+}
+
 static int dummy_file_alloc_security (struct file *file)
 {
 	return 0;
@@ -854,6 +864,7 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, inode_alloc_security);
 	set_to_dummy_if_null(ops, inode_free_security);
 	set_to_dummy_if_null(ops, inode_init_security);
+	set_to_dummy_if_null(ops, inode_lookup);
 	set_to_dummy_if_null(ops, inode_create);
 	set_to_dummy_if_null(ops, inode_link);
 	set_to_dummy_if_null(ops, inode_unlink);
@@ -877,6 +888,7 @@ void security_fixup_ops (struct security
 	set_to_dummy_if_null(ops, inode_setsecurity);
 	set_to_dummy_if_null(ops, inode_listsecurity);
 	set_to_dummy_if_null(ops, file_permission);
+	set_to_dummy_if_null(ops, file_readdir);
 	set_to_dummy_if_null(ops, file_alloc_security);
 	set_to_dummy_if_null(ops, file_free_security);
 	set_to_dummy_if_null(ops, file_ioctl);



This archive was generated by hypermail 2.1.3 : Sat Dec 31 2005 - 10:15:58 PST