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
if (retval < 0)
goto out;
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;
/* execve succeeded */
current->fs->in_exec = 0;
current->in_execve = 0;
+ 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 (!IS_DEADDIR(inode)) {
if (file->f_op->iterate) {
ctx->pos = file->f_pos;
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 {
res = file->f_op->iterate(file, ctx);
file->f_pos = ctx->pos;
} else {
}
EXPORT_SYMBOL(iterate_dir);
}
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..
*
/*
* Traditional linux readdir() handling..
*
buf->result = -EOVERFLOW;
return -EOVERFLOW;
}
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,
buf->result++;
dirent = buf->dirent;
if (!access_ok(VERIFY_WRITE, dirent,
buf->error = -EOVERFLOW;
return -EOVERFLOW;
}
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))
dirent = buf->previous;
if (dirent) {
if (__put_user(offset, &dirent->d_off))
buf->error = -EINVAL; /* only used if we fail.. */
if (reclen > buf->count)
return -EINVAL;
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))
dirent = buf->previous;
if (dirent) {
if (__put_user(offset, &dirent->d_off))
return dentry->d_flags & DCACHE_MOUNTED;
}
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 */
extern int sysctl_vfs_cache_pressure;
#endif /* __LINUX_DCACHE_H */
struct dir_context {
filldir_t actor;
loff_t pos;
struct dir_context {
filldir_t actor;
loff_t pos;
};
static inline bool dir_emit(struct dir_context *ctx,
};
static inline bool dir_emit(struct dir_context *ctx,
#include <asm/processor.h>
#include <linux/rtpm_prio.h>
#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;
struct exec_domain;
struct futex_pi_state;
struct robust_list_head;
#define task_in_mtkpasr(task) unlikely(task->flags & PF_MTKPASR)
#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
/*
* Only the _current_ task can read/write to tsk->flags, but other
* tasks can access tsk->flags in readonly mode for example
#define GLOBAL_ROOT_UID KUIDT_INIT(0)
#define GLOBAL_ROOT_GID KGIDT_INIT(0)
#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)
#define INVALID_UID KUIDT_INIT(-1)
#define INVALID_GID KGIDT_INIT(-1)
}
exit_signals(tsk); /* sets PF_EXITING */
}
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.
/*
* tsk->flags are checked in the futex code to protect against
* an exiting task cleaning up the robust pi futexes.
printk("[%d:%s] fork fail at arch_dup_task_struct, err:%d \n", current->pid, current->comm, err);
goto free_ti;
}
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
/*
tsk->stack = ti;
#ifdef CONFIG_SECCOMP
/*
# include <linux/prio_tracer.h>
#endif
# 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;
void start_bandwidth_timer(struct hrtimer *period_timer, ktime_t period)
{
unsigned long delta;