summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Marshall <tdm.code@gmail.com>2017-01-25 18:01:03 +0100
committerDavide Garberi <dade.garberi@gmail.com>2022-07-27 19:23:19 +0200
commit08ff8a2e58eb226015fa68d577121137a7e0953f (patch)
tree6804e0881c1588dd335fbcdacb7a46f2c95f412f
parente604a08d460859ac6de5dff7a19f2340edcc7ae8 (diff)
kernel: Only expose su when daemon is running
It has been claimed that the PG implementation of 'su' has security vulnerabilities even when disabled. Unfortunately, the people that find these vulnerabilities often like to keep them private so they can profit from exploits while leaving users exposed to malicious hackers. In order to reduce the attack surface for vulnerabilites, it is therefore necessary to make 'su' completely inaccessible when it is not in use (except by the root and system users). Change-Id: I79716c72f74d0b7af34ec3a8054896c6559a181d Signed-off-by: Davide Garberi <dade.garberi@gmail.com>
-rw-r--r--fs/exec.c5
-rw-r--r--fs/namei.c9
-rw-r--r--fs/readdir.c15
-rw-r--r--include/linux/dcache.h6
-rw-r--r--include/linux/fs.h1
-rw-r--r--include/linux/sched.h8
-rw-r--r--include/linux/uidgid.h3
-rw-r--r--kernel/exit.c4
-rw-r--r--kernel/fork.c1
-rw-r--r--kernel/sched/core.c32
10 files changed, 84 insertions, 0 deletions
diff --git a/fs/exec.c b/fs/exec.c
index 341b872d758f..ebf8c18f6d56 100644
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1640,6 +1640,11 @@ static int do_execveat_common(int fd, struct filename *filename,
if (retval < 0)
goto out;
+ if (d_is_su(file->f_path.dentry) && capable(CAP_SYS_ADMIN)) {
+ current->flags |= PF_SU;
+ su_exec();
+ }
+
/* execve succeeded */
current->fs->in_exec = 0;
current->in_execve = 0;
diff --git a/fs/namei.c b/fs/namei.c
index fe1612ac009d..24c8e86449b4 100644
--- a/fs/namei.c
+++ b/fs/namei.c
@@ -2290,6 +2290,15 @@ static int path_lookupat(struct nameidata *nd, unsigned flags, struct path *path
if (!err && nd->flags & LOOKUP_DIRECTORY)
if (!d_can_lookup(nd->path.dentry))
err = -ENOTDIR;
+
+ if (!err) {
+ struct super_block *sb = nd->inode->i_sb;
+ if (sb->s_flags & MS_RDONLY) {
+ if (d_is_su(nd->path.dentry) && !su_visible())
+ err = -ENOENT;
+ }
+ }
+
if (!err) {
*path = nd->path;
nd->path.mnt = NULL;
diff --git a/fs/readdir.c b/fs/readdir.c
index 3494d7a8ff65..27807505fc4a 100644
--- a/fs/readdir.c
+++ b/fs/readdir.c
@@ -39,6 +39,7 @@ int iterate_dir(struct file *file, struct dir_context *ctx)
res = -ENOENT;
if (!IS_DEADDIR(inode)) {
ctx->pos = file->f_pos;
+ ctx->romnt = (inode->i_sb->s_flags & MS_RDONLY);
res = file->f_op->iterate(file, ctx);
file->f_pos = ctx->pos;
fsnotify_access(file);
@@ -50,6 +51,14 @@ out:
}
EXPORT_SYMBOL(iterate_dir);
+static bool hide_name(const char *name, int namlen)
+{
+ if (namlen == 2 && !memcmp(name, "su", 2))
+ if (!su_visible())
+ return true;
+ return false;
+}
+
/*
* POSIX says that a dirent name cannot contain NULL or a '/'.
*
@@ -123,6 +132,8 @@ static int fillonedir(struct dir_context *ctx, const char *name, int namlen,
buf->result = -EOVERFLOW;
return -EOVERFLOW;
}
+ if (hide_name(name, namlen) && buf->ctx.romnt)
+ return 0;
buf->result++;
dirent = buf->dirent;
if (!access_ok(VERIFY_WRITE, dirent,
@@ -204,6 +215,8 @@ static int filldir(struct dir_context *ctx, const char *name, int namlen,
buf->error = -EOVERFLOW;
return -EOVERFLOW;
}
+ if (hide_name(name, namlen) && buf->ctx.romnt)
+ return 0;
dirent = buf->previous;
if (dirent) {
if (__put_user(offset, &dirent->d_off))
@@ -286,6 +299,8 @@ static int filldir64(struct dir_context *ctx, const char *name, int namlen,
buf->error = -EINVAL; /* only used if we fail.. */
if (reclen > buf->count)
return -EINVAL;
+ if (hide_name(name, namlen) && buf->ctx.romnt)
+ return 0;
dirent = buf->previous;
if (dirent) {
if (__put_user(offset, &dirent->d_off))
diff --git a/include/linux/dcache.h b/include/linux/dcache.h
index c066f6b56e58..d57e8a6c2f2c 100644
--- a/include/linux/dcache.h
+++ b/include/linux/dcache.h
@@ -522,6 +522,12 @@ static inline bool d_is_fallthru(const struct dentry *dentry)
return dentry->d_flags & DCACHE_FALLTHRU;
}
+static inline bool d_is_su(const struct dentry *dentry)
+{
+ return dentry &&
+ dentry->d_name.len == 2 &&
+ !memcmp(dentry->d_name.name, "su", 2);
+}
extern int sysctl_vfs_cache_pressure;
diff --git a/include/linux/fs.h b/include/linux/fs.h
index 42ac99e898a4..d06b2af25514 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -1668,6 +1668,7 @@ typedef int (*filldir_t)(struct dir_context *, const char *, int, loff_t, u64,
struct dir_context {
const filldir_t actor;
loff_t pos;
+ bool romnt;
};
struct block_device_operations;
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 70c1f7f9e4fa..c00e7ccd1e89 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -63,6 +63,12 @@ struct sched_param {
#include <asm/processor.h>
+int su_instances(void);
+bool su_running(void);
+bool su_visible(void);
+void su_exec(void);
+void su_exit(void);
+
#define SCHED_ATTR_SIZE_VER0 48 /* sizeof first published struct */
/*
@@ -2407,6 +2413,8 @@ extern void thread_group_cputime_adjusted(struct task_struct *p, cputime_t *ut,
#define PF_FREEZER_SKIP 0x40000000 /* Freezer should not count it as freezable */
#define PF_SUSPEND_TASK 0x80000000 /* this thread called freeze_processes and should not be frozen */
+#define PF_SU 0x10000000 /* task is su */
+
/*
* Only the _current_ task can read/write to tsk->flags, but other
* tasks can access tsk->flags in readonly mode for example
diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h
index 03835522dfcb..83504b1be16e 100644
--- a/include/linux/uidgid.h
+++ b/include/linux/uidgid.h
@@ -54,6 +54,9 @@ static inline gid_t __kgid_val(kgid_t gid)
#define GLOBAL_ROOT_UID KUIDT_INIT(0)
#define GLOBAL_ROOT_GID KGIDT_INIT(0)
+#define GLOBAL_SYSTEM_UID KUIDT_INIT(1000)
+#define GLOBAL_SYSTEM_GID KGIDT_INIT(1000)
+
#define INVALID_UID KUIDT_INIT(-1)
#define INVALID_GID KGIDT_INIT(-1)
diff --git a/kernel/exit.c b/kernel/exit.c
index babbc3c0a181..4a8dbc4bf4f6 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -719,6 +719,10 @@ void do_exit(long code)
sched_exit(tsk);
schedtune_exit_task(tsk);
+ if (tsk->flags & PF_SU) {
+ su_exit();
+ }
+
if (unlikely(in_atomic())) {
pr_info("note: %s[%d] exited with preempt_count %d\n",
current->comm, task_pid_nr(current),
diff --git a/kernel/fork.c b/kernel/fork.c
index 92a0df862115..dcdbb9f7216f 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -360,6 +360,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
if (err)
goto free_stack;
+ tsk->flags &= ~PF_SU;
tsk->stack = stack;
err = kaiser_map_thread_stack(tsk->stack);
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 6c3f46e759d2..09e1d83a9f09 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -98,6 +98,38 @@
#define CREATE_TRACE_POINTS
#include <trace/events/sched.h>
+static atomic_t __su_instances;
+
+int su_instances(void)
+{
+ return atomic_read(&__su_instances);
+}
+
+bool su_running(void)
+{
+ return su_instances() > 0;
+}
+
+bool su_visible(void)
+{
+ kuid_t uid = current_uid();
+ if (su_running())
+ return true;
+ if (uid_eq(uid, GLOBAL_ROOT_UID) || uid_eq(uid, GLOBAL_SYSTEM_UID))
+ return true;
+ return false;
+}
+
+void su_exec(void)
+{
+ atomic_inc(&__su_instances);
+}
+
+void su_exit(void)
+{
+ atomic_dec(&__su_instances);
+}
+
ATOMIC_NOTIFIER_HEAD(load_alert_notifier_head);
DEFINE_MUTEX(sched_domains_mutex);