[RFC] Tainting tasks after poking at them

From: Alexey Dobriyan
Date: Sat Apr 10 2021 - 16:23:06 EST


I'm not a security guy, but

The idea is to irrevocably mark task as tainted after its registers
and/or memory have been changed by another task.

The list definitely includes
* ptrace PTRACE_POKEUSER, PTRACE_POKETEXT, PTRACE_POKEDATA,
PTRACE_SETREGS, PTRACE_SETFPREGS.
* process_vm_writev(2)

If an attacker gets an arbitrary code execution in context of task T,
then every task which can be attached to from T is compromised as well
via registers/memory manipulating system calls.

Tainted flag can be examined in kernel coredumps and maybe even help
with post mortem analysis (no idea if it is really true).

Note:
struct mm_struct should be tainted as well (i've noticed right before
sending this email).

---

arch/x86/kernel/process_64.c | 2 ++
arch/x86/kernel/ptrace.c | 7 +++++++
arch/x86/kernel/tls.c | 2 ++
fs/proc/base.c | 10 ++++++++++
include/linux/sched.h | 14 ++++++++++++++
kernel/ptrace.c | 3 +++
mm/process_vm_access.c | 4 ++++
7 files changed, 42 insertions(+)

--- a/arch/x86/kernel/process_64.c
+++ b/arch/x86/kernel/process_64.c
@@ -468,6 +468,7 @@ void x86_fsbase_write_task(struct task_struct *task, unsigned long fsbase)
WARN_ON_ONCE(task == current);

task->thread.fsbase = fsbase;
+ task_set_tainted(task);
}

void x86_gsbase_write_task(struct task_struct *task, unsigned long gsbase)
@@ -475,6 +476,7 @@ void x86_gsbase_write_task(struct task_struct *task, unsigned long gsbase)
WARN_ON_ONCE(task == current);

task->thread.gsbase = gsbase;
+ task_set_tainted(task);
}

static void
--- a/arch/x86/kernel/ptrace.c
+++ b/arch/x86/kernel/ptrace.c
@@ -214,6 +214,8 @@ static int set_segment_reg(struct task_struct *task,
task_user_gs(task) = value;
}

+ task_set_tainted(task);
+
return 0;
}

@@ -315,6 +317,8 @@ static int set_segment_reg(struct task_struct *task,
break;
}

+ task_set_tainted(task);
+
return 0;
}

@@ -349,6 +353,8 @@ static int set_flags(struct task_struct *task, unsigned long value)

regs->flags = (regs->flags & ~FLAG_MASK) | (value & FLAG_MASK);

+ task_set_tainted(task);
+
return 0;
}

@@ -382,6 +388,7 @@ static int putreg(struct task_struct *child,
}

*pt_regs_access(task_pt_regs(child), offset) = value;
+ task_set_tainted(child);
return 0;
}

--- a/arch/x86/kernel/tls.c
+++ b/arch/x86/kernel/tls.c
@@ -106,6 +106,8 @@ static void set_tls_desc(struct task_struct *p, int idx,
load_TLS(t, cpu);

put_cpu();
+
+ task_set_tainted(p);
}

/*
--- a/fs/proc/base.c
+++ b/fs/proc/base.c
@@ -3149,6 +3149,14 @@ static int proc_stack_depth(struct seq_file *m, struct pid_namespace *ns,
}
#endif /* CONFIG_STACKLEAK_METRICS */

+static int proc_pid_tainted(struct seq_file *m, struct pid_namespace *_,
+ struct pid *__, struct task_struct *tsk)
+{
+ seq_putc(m, '0' + task_is_tainted(tsk));
+ seq_putc(m, '\n');
+ return 0;
+}
+
/*
* Thread groups
*/
@@ -3265,6 +3273,7 @@ static const struct pid_entry tgid_base_stuff[] = {
#ifdef CONFIG_SECCOMP_CACHE_DEBUG
ONE("seccomp_cache", S_IRUSR, proc_pid_seccomp_cache),
#endif
+ ONE("tainted", S_IRUGO, proc_pid_tainted),
};

static int proc_tgid_base_readdir(struct file *file, struct dir_context *ctx)
@@ -3598,6 +3607,7 @@ static const struct pid_entry tid_base_stuff[] = {
#ifdef CONFIG_SECCOMP_CACHE_DEBUG
ONE("seccomp_cache", S_IRUSR, proc_pid_seccomp_cache),
#endif
+ ONE("tainted", S_IRUGO, proc_pid_tainted),
};

static int proc_tid_base_readdir(struct file *file, struct dir_context *ctx)
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -668,6 +668,7 @@ struct task_struct {
/* Per task flags (PF_*), defined further below: */
unsigned int flags;
unsigned int ptrace;
+ bool tainted;

#ifdef CONFIG_SMP
int on_cpu;
@@ -2026,6 +2027,19 @@ extern long sched_getaffinity(pid_t pid, struct cpumask *mask);
unsigned long sched_cpu_util(int cpu, unsigned long max);
#endif /* CONFIG_SMP */

+static inline bool task_is_tainted(const struct task_struct *tsk)
+{
+ return READ_ONCE(tsk->tainted);
+}
+
+static inline void task_set_tainted(struct task_struct *tsk)
+{
+ /* Self-flagellation is OK. */
+ if (tsk != current) {
+ WRITE_ONCE(tsk->tainted, true);
+ }
+}
+
#ifdef CONFIG_RSEQ

/*
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -1297,6 +1297,9 @@ int generic_ptrace_pokedata(struct task_struct *tsk, unsigned long addr,

copied = ptrace_access_vm(tsk, addr, &data, sizeof(data),
FOLL_FORCE | FOLL_WRITE);
+ if (copied > 0) {
+ task_set_tainted(tsk);
+ }
return (copied == sizeof(data)) ? 0 : -EIO;
}

--- a/mm/process_vm_access.c
+++ b/mm/process_vm_access.c
@@ -228,6 +228,10 @@ static ssize_t process_vm_rw_core(pid_t pid, struct iov_iter *iter,

mmput(mm);

+ if (vm_write && rc > 0) {
+ task_set_tainted(task);
+ }
+
put_task_struct:
put_task_struct(task);