[PATCH] shmem: add support for user extended attributes

From: Oleksandr Tymoshenko
Date: Thu Jul 20 2023 - 02:55:07 EST


User extended attributes are not enabled in tmpfs because
the size of the value is not limited and the memory allocated
for it is not counted against any limit. Malicious
non-privileged user can exhaust kernel memory by creating
user.* extended attribute with very large value.

There are still situations when enabling suport for extended
user attributes on tmpfs is required and the attack vector
is not applicable, for instance batch jobs with trusted binaries.

This patch introduces two mount options to enable/disable
support for user.* extended attributes on tmpfs:

user_xattr enable support for user extended aatributes
nouser_xattr disable support for user extended attributes

The default behavior of the filesystem is not changed.

Signed-off-by: Oleksandr Tymoshenko <ovt@xxxxxxxxxx>
---
Documentation/filesystems/tmpfs.rst | 12 ++++++++
include/linux/shmem_fs.h | 1 +
mm/shmem.c | 45 +++++++++++++++++++++++++++++
3 files changed, 58 insertions(+)

diff --git a/Documentation/filesystems/tmpfs.rst b/Documentation/filesystems/tmpfs.rst
index f18f46be5c0c..5700ba72d095 100644
--- a/Documentation/filesystems/tmpfs.rst
+++ b/Documentation/filesystems/tmpfs.rst
@@ -215,6 +215,16 @@ will give you tmpfs instance on /mytmpfs which can allocate 10GB
RAM/SWAP in 10240 inodes and it is only accessible by root.


+tmpfs, when compiled with CONFIG_TMPFS_XATTR, does not support
+Extended User Attributes for security reasons. The support can be
+enabled/disabled by two mount options:
+
+============ ===========================================
+user_xattr Enable support for Extended User Attributes
+nouser_xattr Disable upport for Extended User Attributes
+============ ===========================================
+
+
:Author:
Christoph Rohland <cr@xxxxxxx>, 1.12.01
:Updated:
@@ -223,3 +233,5 @@ RAM/SWAP in 10240 inodes and it is only accessible by root.
KOSAKI Motohiro, 16 Mar 2010
:Updated:
Chris Down, 13 July 2020
+:Updated:
+ Oleksandr Tymoshenko, 19 July 2023
diff --git a/include/linux/shmem_fs.h b/include/linux/shmem_fs.h
index 9029abd29b1c..f06d18b9041c 100644
--- a/include/linux/shmem_fs.h
+++ b/include/linux/shmem_fs.h
@@ -53,6 +53,7 @@ struct shmem_sb_info {
spinlock_t shrinklist_lock; /* Protects shrinklist */
struct list_head shrinklist; /* List of shinkable inodes */
unsigned long shrinklist_len; /* Length of shrinklist */
+ bool user_xattr; /* user.* xattrs are allowed */
};

static inline struct shmem_inode_info *SHMEM_I(struct inode *inode)
diff --git a/mm/shmem.c b/mm/shmem.c
index 2f2e0e618072..4f7d46d65494 100644
--- a/mm/shmem.c
+++ b/mm/shmem.c
@@ -85,6 +85,7 @@ static struct vfsmount *shm_mnt;

#define BLOCKS_PER_PAGE (PAGE_SIZE/512)
#define VM_ACCT(size) (PAGE_ALIGN(size) >> PAGE_SHIFT)
+#define TMPFS_USER_XATTR_INDEX 1

/* Pretend that each entry is of this size in directory's i_size */
#define BOGO_DIRENT_SIZE 20
@@ -116,11 +117,13 @@ struct shmem_options {
int huge;
int seen;
bool noswap;
+ bool user_xattr;
#define SHMEM_SEEN_BLOCKS 1
#define SHMEM_SEEN_INODES 2
#define SHMEM_SEEN_HUGE 4
#define SHMEM_SEEN_INUMS 8
#define SHMEM_SEEN_NOSWAP 16
+#define SHMEM_SEEN_USER_XATTR 32
};

#ifdef CONFIG_TMPFS
@@ -3447,6 +3450,16 @@ static int shmem_xattr_handler_get(const struct xattr_handler *handler,
const char *name, void *buffer, size_t size)
{
struct shmem_inode_info *info = SHMEM_I(inode);
+ struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
+
+ switch (handler->flags) {
+ case TMPFS_USER_XATTR_INDEX:
+ if (!sbinfo->user_xattr)
+ return -EOPNOTSUPP;
+ break;
+ default:
+ break;
+ }

name = xattr_full_name(handler, name);
return simple_xattr_get(&info->xattrs, name, buffer, size);
@@ -3459,8 +3472,18 @@ static int shmem_xattr_handler_set(const struct xattr_handler *handler,
size_t size, int flags)
{
struct shmem_inode_info *info = SHMEM_I(inode);
+ struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb);
int err;

+ switch (handler->flags) {
+ case TMPFS_USER_XATTR_INDEX:
+ if (!sbinfo->user_xattr)
+ return -EOPNOTSUPP;
+ break;
+ default:
+ break;
+ }
+
name = xattr_full_name(handler, name);
err = simple_xattr_set(&info->xattrs, name, value, size, flags, NULL);
if (!err) {
@@ -3482,9 +3505,17 @@ static const struct xattr_handler shmem_trusted_xattr_handler = {
.set = shmem_xattr_handler_set,
};

+static const struct xattr_handler shmem_user_xattr_handler = {
+ .prefix = XATTR_USER_PREFIX,
+ .flags = TMPFS_USER_XATTR_INDEX,
+ .get = shmem_xattr_handler_get,
+ .set = shmem_xattr_handler_set,
+};
+
static const struct xattr_handler *shmem_xattr_handlers[] = {
&shmem_security_xattr_handler,
&shmem_trusted_xattr_handler,
+ &shmem_user_xattr_handler,
NULL
};

@@ -3604,6 +3635,8 @@ enum shmem_param {
Opt_inode32,
Opt_inode64,
Opt_noswap,
+ Opt_user_xattr,
+ Opt_nouser_xattr,
};

static const struct constant_table shmem_param_enums_huge[] = {
@@ -3626,6 +3659,8 @@ const struct fs_parameter_spec shmem_fs_parameters[] = {
fsparam_flag ("inode32", Opt_inode32),
fsparam_flag ("inode64", Opt_inode64),
fsparam_flag ("noswap", Opt_noswap),
+ fsparam_flag ("user_xattr", Opt_user_xattr),
+ fsparam_flag ("nouser_xattr", Opt_nouser_xattr),
{}
};

@@ -3717,6 +3752,14 @@ static int shmem_parse_one(struct fs_context *fc, struct fs_parameter *param)
ctx->noswap = true;
ctx->seen |= SHMEM_SEEN_NOSWAP;
break;
+ case Opt_user_xattr:
+ ctx->user_xattr = true;
+ ctx->seen |= SHMEM_SEEN_USER_XATTR;
+ break;
+ case Opt_nouser_xattr:
+ ctx->user_xattr = false;
+ ctx->seen |= SHMEM_SEEN_USER_XATTR;
+ break;
}
return 0;

@@ -3834,6 +3877,8 @@ static int shmem_reconfigure(struct fs_context *fc)
sbinfo->max_inodes = ctx->inodes;
sbinfo->free_inodes = ctx->inodes - inodes;
}
+ if (ctx->seen & SHMEM_SEEN_USER_XATTR)
+ sbinfo->user_xattr = ctx->user_xattr;

/*
* Preserve previous mempolicy unless mpol remount option was specified.
--
2.41.0.255.g8b1d071c50-goog