Unverified Commit 41e8149c authored by Adrian Ratiu's avatar Adrian Ratiu Committed by Christian Brauner
Browse files

proc: add config & param to block forcing mem writes

This adds a Kconfig option and boot param to allow removing
the FOLL_FORCE flag from /proc/pid/mem write calls because
it can be abused.

The traditional forcing behavior is kept as default because
it can break GDB and some other use cases.

Previously we tried a more sophisticated approach allowing
distributions to fine-tune /proc/pid/mem behavior, however
that got NAK-ed by Linus [1], who prefers this simpler
approach with semantics also easier to understand for users.

Link: https://lore.kernel.org/lkml/CAHk-=wiGWLChxYmUA5HrT5aopZrB7_2VTa0NLZcxORgkUe5tEQ@mail.gmail.com/

 [1]
Cc: Doug Anderson <dianders@chromium.org>
Cc: Jeff Xu <jeffxu@google.com>
Cc: Jann Horn <jannh@google.com>
Cc: Kees Cook <kees@kernel.org>
Cc: Ard Biesheuvel <ardb@kernel.org>
Cc: Christian Brauner <brauner@kernel.org>
Suggested-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: default avatarAdrian Ratiu <adrian.ratiu@collabora.com>
Link: https://lore.kernel.org/r/20240802080225.89408-1-adrian.ratiu@collabora.com


Signed-off-by: default avatarChristian Brauner <brauner@kernel.org>
parent 8400291e
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -4788,6 +4788,16 @@
	printk.time=	Show timing data prefixed to each printk message line
			Format: <bool>  (1/Y/y=enable, 0/N/n=disable)

	proc_mem.force_override= [KNL]
			Format: {always | ptrace | never}
			Traditionally /proc/pid/mem allows memory permissions to be
			overridden without restrictions. This option may be set to
			restrict that. Can be one of:
			- 'always': traditional behavior always allows mem overrides.
			- 'ptrace': only allow mem overrides for active ptracers.
			- 'never':  never allow mem overrides.
			If not specified, default is the CONFIG_PROC_MEM_* choice.

	processor.max_cstate=	[HW,ACPI]
			Limit processor to maximum C-state
			max_cstate=9 overrides any DMI blacklist limit.
+60 −1
Original line number Diff line number Diff line
@@ -85,6 +85,7 @@
#include <linux/elf.h>
#include <linux/pid_namespace.h>
#include <linux/user_namespace.h>
#include <linux/fs_parser.h>
#include <linux/fs_struct.h>
#include <linux/slab.h>
#include <linux/sched/autogroup.h>
@@ -117,6 +118,40 @@
static u8 nlink_tid __ro_after_init;
static u8 nlink_tgid __ro_after_init;

enum proc_mem_force {
	PROC_MEM_FORCE_ALWAYS,
	PROC_MEM_FORCE_PTRACE,
	PROC_MEM_FORCE_NEVER
};

static enum proc_mem_force proc_mem_force_override __ro_after_init =
	IS_ENABLED(CONFIG_PROC_MEM_NO_FORCE) ? PROC_MEM_FORCE_NEVER :
	IS_ENABLED(CONFIG_PROC_MEM_FORCE_PTRACE) ? PROC_MEM_FORCE_PTRACE :
	PROC_MEM_FORCE_ALWAYS;

static const struct constant_table proc_mem_force_table[] __initconst = {
	{ "always", PROC_MEM_FORCE_ALWAYS },
	{ "ptrace", PROC_MEM_FORCE_PTRACE },
	{ "never", PROC_MEM_FORCE_NEVER },
	{ }
};

static int __init early_proc_mem_force_override(char *buf)
{
	if (!buf)
		return -EINVAL;

	/*
	 * lookup_constant() defaults to proc_mem_force_override to preseve
	 * the initial Kconfig choice in case an invalid param gets passed.
	 */
	proc_mem_force_override = lookup_constant(proc_mem_force_table,
						  buf, proc_mem_force_override);

	return 0;
}
early_param("proc_mem.force_override", early_proc_mem_force_override);

struct pid_entry {
	const char *name;
	unsigned int len;
@@ -835,6 +870,28 @@ static int mem_open(struct inode *inode, struct file *file)
	return ret;
}

static bool proc_mem_foll_force(struct file *file, struct mm_struct *mm)
{
	struct task_struct *task;
	bool ptrace_active = false;

	switch (proc_mem_force_override) {
	case PROC_MEM_FORCE_NEVER:
		return false;
	case PROC_MEM_FORCE_PTRACE:
		task = get_proc_task(file_inode(file));
		if (task) {
			ptrace_active =	READ_ONCE(task->ptrace) &&
					READ_ONCE(task->mm) == mm &&
					READ_ONCE(task->parent) == current;
			put_task_struct(task);
		}
		return ptrace_active;
	default:
		return true;
	}
}

static ssize_t mem_rw(struct file *file, char __user *buf,
			size_t count, loff_t *ppos, int write)
{
@@ -855,7 +912,9 @@ static ssize_t mem_rw(struct file *file, char __user *buf,
	if (!mmget_not_zero(mm))
		goto free;

	flags = FOLL_FORCE | (write ? FOLL_WRITE : 0);
	flags = write ? FOLL_WRITE : 0;
	if (proc_mem_foll_force(file, mm))
		flags |= FOLL_FORCE;

	while (count > 0) {
		size_t this_len = min_t(size_t, count, PAGE_SIZE);
+32 −0
Original line number Diff line number Diff line
@@ -19,6 +19,38 @@ config SECURITY_DMESG_RESTRICT

	  If you are unsure how to answer this question, answer N.

choice
	prompt "Allow /proc/pid/mem access override"
	default PROC_MEM_ALWAYS_FORCE
	help
	  Traditionally /proc/pid/mem allows users to override memory
	  permissions for users like ptrace, assuming they have ptrace
	  capability.

	  This allows people to limit that - either never override, or
	  require actual active ptrace attachment.

	  Defaults to the traditional behavior (for now)

config PROC_MEM_ALWAYS_FORCE
	bool "Traditional /proc/pid/mem behavior"
	help
	  This allows /proc/pid/mem accesses to override memory mapping
	  permissions if you have ptrace access rights.

config PROC_MEM_FORCE_PTRACE
	bool "Require active ptrace() use for access override"
	help
	  This allows /proc/pid/mem accesses to override memory mapping
	  permissions for active ptracers like gdb.

config PROC_MEM_NO_FORCE
	bool "Never"
	help
	  Never override memory mapping permissions

endchoice

config SECURITY
	bool "Enable different security models"
	depends on SYSFS