kernel: Only expose su when daemon is running
authorTom Marshall <tdm.code@gmail.com>
Wed, 25 Jan 2017 17:01:03 +0000 (18:01 +0100)
committerStricted <info@stricted.net>
Thu, 3 May 2018 16:44:36 +0000 (18:44 +0200)
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

fs/exec.c
fs/namei.c
fs/readdir.c
include/linux/dcache.h
include/linux/fs.h
include/linux/sched.h
include/linux/uidgid.h
kernel/exit.c
kernel/fork.c
kernel/sched/core.c

index fd2778918e86af49d1c99c4bfec6a05d476e43c5..62de118065e25396ee40168840ab352289a94604 100644 (file)
--- a/fs/exec.c
+++ b/fs/exec.c
@@ -1587,6 +1587,11 @@ static int do_execve_common(const char *filename,
        if (retval < 0)
                goto out;
 
+       if (d_is_su(file->f_dentry) && capable(CAP_SYS_ADMIN)) {
+               current->flags |= PF_SU;
+               su_exec();
+       }
+
        /* execve succeeded */
        current->fs->in_exec = 0;
        current->in_execve = 0;
index c87e15ee925594ce75ef3ae3b1c62dd789cae1e8..36d4b29459ecb52a713c1cc3565e426a36e43c77 100644 (file)
@@ -2009,6 +2009,14 @@ static int path_lookupat(int dfd, const char *name,
                }
        }
 
+       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 (base)
                fput(base);
 
index 5d6578affbbf91b6d2e80b32398e8f0a53bc08ae..516fc904513dbceea76bba4bec351cf102d66230 100644 (file)
@@ -39,6 +39,7 @@ int iterate_dir(struct file *file, struct dir_context *ctx)
        if (!IS_DEADDIR(inode)) {
                if (file->f_op->iterate) {
                        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;
                } else {
@@ -53,6 +54,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;
+}
+
 /*
  * Traditional linux readdir() handling..
  *
@@ -91,6 +100,8 @@ static int fillonedir(void * __buf, const char * name, int namlen, loff_t offset
                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,
@@ -169,6 +180,8 @@ static int filldir(void * __buf, const char * name, int namlen, loff_t offset,
                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))
@@ -249,6 +262,8 @@ static int filldir64(void * __buf, const char * name, int namlen, loff_t offset,
        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))
index c1999d1fe6f8dd7deed810ed9cf18623bf10c3a6..65a8fb6c769c76af320a5e7a93611b743d727299 100644 (file)
@@ -410,6 +410,13 @@ static inline bool d_mountpoint(struct dentry *dentry)
        return dentry->d_flags & DCACHE_MOUNTED;
 }
 
+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;
 
 #endif /* __LINUX_DCACHE_H */
index 3fe45fa4b78cc7647f8167fdec25743fdc651807..0d4d337e723a5b9702b5ce2a6f35d907d730a246 100644 (file)
@@ -1509,6 +1509,7 @@ typedef int (*filldir_t)(void *, const char *, int, loff_t, u64, unsigned);
 struct dir_context {
        filldir_t actor;
        loff_t pos;
+       bool romnt;
 };
 
 static inline bool dir_emit(struct dir_context *ctx,
index 7d5ffda71119b664757cea2d117c1790ea70bdb6..b290b0c030126be314790232ea423b046c2e7981 100644 (file)
@@ -56,6 +56,12 @@ struct sched_param {
 #include <asm/processor.h>
 #include <linux/rtpm_prio.h>
 
+int  su_instances(void);
+bool su_running(void);
+bool su_visible(void);
+void su_exec(void);
+void su_exit(void);
+
 struct exec_domain;
 struct futex_pi_state;
 struct robust_list_head;
@@ -1774,6 +1780,8 @@ extern int task_free_unregister(struct notifier_block *n);
 
 #define task_in_mtkpasr(task)  unlikely(task->flags & PF_MTKPASR)
 
+#define PF_SU          0x00000002      /* 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
index 8e522cbcef29f6472320aaaa05eda771b388206b..cb4c867a523f8b7d5691acfdeb847ec9e382d59f 100644 (file)
@@ -64,6 +64,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)
 
index b75ebb365ce5ff39c27872e982ab47fc2001c64f..72bde1db3bd2291e18425b8d26e2470666ce88eb 100644 (file)
@@ -778,6 +778,11 @@ void do_exit(long code)
        }
 
        exit_signals(tsk);  /* sets PF_EXITING */
+
+       if (tsk->flags & PF_SU) {
+               su_exit();
+       }
+
        /*
         * tsk->flags are checked in the futex code to protect against
         * an exiting task cleaning up the robust pi futexes.
index 8b32b9205cf7d614d0231e617a8ec97bd0a7afff..99408165ded413542653e31147f40e21d4dae1fd 100644 (file)
@@ -334,6 +334,9 @@ static struct task_struct *dup_task_struct(struct task_struct *orig)
                printk("[%d:%s] fork fail at arch_dup_task_struct, err:%d \n", current->pid, current->comm, err);
                goto free_ti;
        }
+
+       tsk->flags &= ~PF_SU;
+
        tsk->stack = ti;
 #ifdef CONFIG_SECCOMP
        /*
index 3c359e0c83808e9a7802b1e7f6d25c8fd943ed14..b817cf43ff44bac1e4ae2119c003b27a073a165f 100644 (file)
 # include <linux/prio_tracer.h>
 #endif
 
+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);
+}
+
 void start_bandwidth_timer(struct hrtimer *period_timer, ktime_t period)
 {
        unsigned long delta;