[RFC PATCH v1 07/13] exec: validate spawn template executable identity

Li Chen <[email protected]>
Newsgroups org.kernel.vger.linux-api,org.kernel.vger.linux-arch,org.kernel.vger.linux-doc,org.kernel.vger.linux-fsdevel,org.kernel.vger.linux-kernel,org.kernel.vger.linux-kselftest,org.kvack.linux-mm
Message-ID <[email protected]>
Record a conservative executable identity key when a template is created:
device, inode, size, mode, owner, ctime, and mtime. Recheck it before
each spawn. For path-created templates, also reopen the path so a replaced
executable cannot silently reuse the old template fd.
Reject stale templates with ESTALE. Keep the check conservative by also
rechecking that the file remains a regular executable mapping target.

Signed-off-by: Li Chen <[email protected]>
---
 MAINTAINERS                    |  1 +
 fs/spawn_template.c            | 75 ++++++++++++++++++++++++++++++++++
 include/linux/spawn_template.h | 25 ++++++++++++
 3 files changed, 101 insertions(+)
 create mode 100644 include/linux/spawn_template.h

diff --git a/MAINTAINERS b/MAINTAINERS
index d5441812825c3..ea4134a188779 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9737,6 +9737,7 @@ F:	fs/tests/binfmt_*_kunit.c
 F:	fs/tests/exec_kunit.c
 F:	include/linux/binfmts.h
 F:	include/linux/elf.h
+F:	include/linux/spawn_template.h
 F:	include/uapi/linux/auxvec.h
 F:	include/uapi/linux/binfmts.h
 F:	include/uapi/linux/elf.h
diff --git a/fs/spawn_template.c b/fs/spawn_template.c
index 8c3711929cffb..268f804227987 100644
--- a/fs/spawn_template.c
+++ b/fs/spawn_template.c
@@ -15,6 +15,7 @@
 #include <linux/sched/task.h>
 #include <linux/signal.h>
 #include <linux/slab.h>
+#include <linux/spawn_template.h>
 #include <linux/string.h>
 #include <linux/syscalls.h>
 #include <linux/uaccess.h>
@@ -27,6 +28,7 @@
 
 struct spawn_template {
 	struct file *exec_file;
+	struct spawn_template_file_key exec_key;
 	const struct cred *creator_cred;
 	char *filename;
 	bool deny_write;
@@ -40,6 +42,46 @@ struct spawn_template_spawn_context {
 
 static const struct file_operations spawn_template_fops;
 
+static bool spawn_template_file_exec_allowed(struct file *file);
+
+void spawn_template_fill_file_key(struct file *file,
+				  struct spawn_template_file_key *key)
+{
+	struct inode *inode = file_inode(file);
+	struct timespec64 ctime = inode_get_ctime(inode);
+	struct timespec64 mtime = inode_get_mtime(inode);
+
+	key->dev = inode->i_sb->s_dev;
+	key->ino = inode->i_ino;
+	key->size = i_size_read(inode);
+	key->mode = READ_ONCE(inode->i_mode);
+	key->uid = inode->i_uid;
+	key->gid = inode->i_gid;
+	key->ctime_sec = ctime.tv_sec;
+	key->ctime_nsec = ctime.tv_nsec;
+	key->mtime_sec = mtime.tv_sec;
+	key->mtime_nsec = mtime.tv_nsec;
+}
+
+bool spawn_template_file_key_matches(struct file *file,
+				     const struct spawn_template_file_key *key)
+{
+	struct spawn_template_file_key cur;
+
+	spawn_template_fill_file_key(file, &cur);
+
+	return cur.dev == key->dev &&
+	       cur.ino == key->ino &&
+	       cur.size == key->size &&
+	       cur.mode == key->mode &&
+	       uid_eq(cur.uid, key->uid) &&
+	       gid_eq(cur.gid, key->gid) &&
+	       cur.ctime_sec == key->ctime_sec &&
+	       cur.ctime_nsec == key->ctime_nsec &&
+	       cur.mtime_sec == key->mtime_sec &&
+	       cur.mtime_nsec == key->mtime_nsec;
+}
+
 static int spawn_template_exit_status(int err)
 {
 	switch (err) {
@@ -58,6 +100,32 @@ static bool spawn_template_cred_matches(struct spawn_template *tmpl)
 	return current_cred() == tmpl->creator_cred;
 }
 
+static bool spawn_template_key_matches(struct spawn_template *tmpl)
+{
+	bool matches;
+
+	if (tmpl->filename) {
+		struct file *file __free(fput) = NULL;
+		struct file *tmp;
+
+		tmp = open_exec(tmpl->filename);
+		if (IS_ERR(tmp))
+			return false;
+		file = tmp;
+
+		matches = spawn_template_file_key_matches(file,
+							  &tmpl->exec_key);
+		matches = matches && spawn_template_file_exec_allowed(file);
+		exe_file_allow_write_access(file);
+		if (!matches)
+			return false;
+	}
+
+	return spawn_template_file_exec_allowed(tmpl->exec_file) &&
+	       spawn_template_file_key_matches(tmpl->exec_file,
+					       &tmpl->exec_key);
+}
+
 static int spawn_template_copy_signal_set(const struct spawn_template_action *action,
 					  sigset_t *mask)
 {
@@ -433,6 +501,7 @@ SYSCALL_DEFINE2(spawn_template_create,
 						 &tmpl->deny_write);
 	if (ret)
 		goto out_free_tmpl;
+	spawn_template_fill_file_key(tmpl->exec_file, &tmpl->exec_key);
 
 	if (args.flags & SPAWN_TEMPLATE_CREATE_CLOEXEC)
 		fd_flags |= O_CLOEXEC;
@@ -507,6 +576,11 @@ SYSCALL_DEFINE3(spawn_template_spawn, int, template_fd,
 	if (ret)
 		goto out_free_ctx;
 
+	if (!spawn_template_key_matches(ctx->tmpl)) {
+		ret = -ESTALE;
+		goto out_free_actions;
+	}
+
 	kargs = (struct kernel_clone_args) {
 		.flags		= CLONE_VM | CLONE_VFORK | CLONE_PIDFD,
 		.pidfd		= u64_to_user_ptr(ctx->args.pidfd),
@@ -517,6 +591,7 @@ SYSCALL_DEFINE3(spawn_template_spawn, int, template_fd,
 
 	ret = kernel_clone(&kargs);
 
+out_free_actions:
 	kfree(ctx->actions);
 out_free_ctx:
 	kfree(ctx);
diff --git a/include/linux/spawn_template.h b/include/linux/spawn_template.h
new file mode 100644
index 0000000000000..f14a7749fe55b
--- /dev/null
+++ b/include/linux/spawn_template.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_SPAWN_TEMPLATE_H
+#define _LINUX_SPAWN_TEMPLATE_H
+
+#include <linux/fs.h>
+
+struct spawn_template_file_key {
+	dev_t dev;
+	ino_t ino;
+	loff_t size;
+	umode_t mode;
+	kuid_t uid;
+	kgid_t gid;
+	u64 ctime_sec;
+	u64 ctime_nsec;
+	u64 mtime_sec;
+	u64 mtime_nsec;
+};
+
+void spawn_template_fill_file_key(struct file *file,
+				  struct spawn_template_file_key *key);
+bool spawn_template_file_key_matches(struct file *file,
+				     const struct spawn_template_file_key *key);
+
+#endif /* _LINUX_SPAWN_TEMPLATE_H */
-- 
2.52.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.