InfoSect's Month of Pointless Bugs (#3)

Dr Silvio Cesare

InfoSect, Canberra's hackerspace, regularly runs public group sessions to perform code review and vulnerability discovery. Over the next 30 days, I'll highlight the source code of 30 unknown vulnerabilities.

Bug #3

In the rp-pppoe package src/common.c

void
dropPrivs(void)
{
    struct passwd *pw = NULL;
    int ok = 0;
    if (geteuid() == 0) {
        pw = getpwnam("nobody");
        if (pw) {
            if (setgid(pw->pw_gid) < 0) ok++;
            if (setuid(pw->pw_uid) < 0) ok++;
        }
    }
    if (ok < 2 && IsSetID) {
        setegid(getgid());
        seteuid(getuid());
    }
}

Note that setgid/setegid and seuid/seteuid do not check the return value. If these calls fail, privileges are not dropped. Do they fail though?

In Linux kernel kernel/sys.c

SYSCALL_DEFINE1(setuid, uid_t, uid)
{
        struct user_namespace *ns = current_user_ns();
        const struct cred *old;
        struct cred *new;
        int retval;
        kuid_t kuid;

        kuid = make_kuid(ns, uid);
        if (!uid_valid(kuid))
                return -EINVAL;

        new = prepare_creds();
        if (!new)
                return -ENOMEM;
        old = current_cred();

Setuid and setgid can fail.
What about seteuid/setegid? Lets look at glibc:

int
seteuid (uid_t uid)
{
  int result;

  if (uid == (uid_t) ~0)
    return INLINE_SYSCALL_ERROR_RETURN_VALUE (EINVAL);

#ifdef __NR_setresuid32
  result = INLINE_SETXID_SYSCALL (setresuid32, 3, -1, uid, -1);
#else
  result = INLINE_SETXID_SYSCALL (setresuid, 3, -1, uid, -1);
#endif

  return result;
}

So it calls setresuid. Lets look at the kernel code kernel/sys.c:

SYSCALL_DEFINE2(setreuid, uid_t, ruid, uid_t, euid)
{
        struct user_namespace *ns = current_user_ns();
        const struct cred *old;
        struct cred *new;
        int retval;
        kuid_t kruid, keuid;

        kruid = make_kuid(ns, ruid);
        keuid = make_kuid(ns, euid);

        if ((ruid != (uid_t) -1) && !uid_valid(kruid))
                return -EINVAL;
        if ((euid != (uid_t) -1) && !uid_valid(keuid))
                return -EINVAL;

        new = prepare_creds();
        if (!new)
                return -ENOMEM;
        old = current_cred();

And in kernel/cred.c

struct cred *prepare_creds(void)
{
        struct task_struct *task = current;
        const struct cred *old;
        struct cred *new;

        validate_process_creds();

        new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
        if (!new)
                return NULL;

It's clear that, theoretically, setreuid/seteuid/setuid (and gid)* can fail if kernel memory allocation fails. Hence, dropping privs can fail.

Popular posts from this blog

Empowering Women in Cybersecurity: InfoSect's 2024 Training Initiative

C++ Memory Corruption (std::string) - part 4

Pointer Compression in V8