diff --git a/fs/exec.c b/fs/exec.c index fd2778918e8..62de118065e 100644 --- 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; diff --git a/fs/namei.c b/fs/namei.c index c87e15ee925..36d4b29459e 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -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); diff --git a/fs/readdir.c b/fs/readdir.c index 5d6578affbb..516fc904513 100644 --- a/fs/readdir.c +++ b/fs/readdir.c @@ -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)) diff --git a/include/linux/dcache.h b/include/linux/dcache.h index c1999d1fe6f..65a8fb6c769 100644 --- a/include/linux/dcache.h +++ b/include/linux/dcache.h @@ -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 */ diff --git a/include/linux/fs.h b/include/linux/fs.h index 3fe45fa4b78..0d4d337e723 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -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, diff --git a/include/linux/sched.h b/include/linux/sched.h index 7d5ffda7111..b290b0c0301 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -56,6 +56,12 @@ struct sched_param { #include #include +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 diff --git a/include/linux/uidgid.h b/include/linux/uidgid.h index 8e522cbcef2..cb4c867a523 100644 --- a/include/linux/uidgid.h +++ b/include/linux/uidgid.h @@ -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) diff --git a/kernel/exit.c b/kernel/exit.c index b75ebb365ce..72bde1db3bd 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -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. diff --git a/kernel/fork.c b/kernel/fork.c index 8b32b9205cf..99408165ded 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -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 /* diff --git a/kernel/sched/core.c b/kernel/sched/core.c index 3c359e0c838..b817cf43ff4 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c @@ -102,6 +102,38 @@ # include #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;