0

I am having trouble understanding why my setuid program doesn't seem like it is actually elevating permissions, even though the id's seem correct. This is running on a 2.6 kernel and fails, but works exactly as intended on Ubuntu 14.04 doing the same thing. I need a program that at certain times during execution needs elevated permissions, while least privilege is the default.

#include <stdio.h>
#include <stdint.h>
#include <arpa/inet.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

static uid_t _orig_euid;

void save_privilege(void){
    _orig_euid = geteuid();
    printf("saved privilege: %d\n", _orig_euid);
}

void drop_privileges(void){
    if(seteuid(getuid()) == -1){
        exit(0);
    }
    printf("dropped privileges %d %d\n", getuid(), geteuid());
}

void reacquire_privileges(void){
    if(setuid(_orig_euid) == -1){
        exit(0);
    }
    printf("reacquired privileges %d %d\n", getuid(), geteuid());
}

void do_privileged(int rw){
    switch(rw){
        case 0:
            //read from driver
            system("dd if=/dev/myrandom bs=10 count=1");
        case 1:
            //write to driver
            system("dd if=/dev/zero of=/dev/myrandom");
        case 2:
            //change something in proc fs
            system("echo 3 > /proc/sys/vm/drop_caches");
        default:
            break;
    }
}

int main(int argc, char *argv[]){
    int i;

    if(argc != 2){
        printf("usage: %s testno\n", argv[0]);
        return 0;
    }

    i = atoi(argv[1]);

    save_privilege();

    do_privileged(i);

    drop_privileges();

    do_privileged(i);

    reacquire_privileges();

    do_privileged(i);

    return 0;
}

My program permissions are set as:

ls -l
-rwsr-xr-x    1 root     root         6547 Sep 13 00:35 test

My current user id is:

id
uid=1000(user) gid=1000(user)

The procfs entry I'm trying to write to is:

ls -l /proc/sys/vm/drop_caches
-rw-r--r--    1 root     root            0 Sep 13 00:36 /proc/sys/vm/drop_caches

When I run the program, I get:

./test 2
saved privilege: 0
sh: cannot create /proc/sys/vm/drop_caches: Permission denied
dropped privileges 1000 1000
sh: cannot create /proc/sys/vm/drop_caches: Permission denied
reacquired privileges 1000 0
sh: cannot create /proc/sys/vm/drop_caches: Permission denied

However, running the same program on Ubuntu 14.04 works correctly - it only fails to modify the procfs entry when privileges are dropped.

Here is an strace (./test_perm is the same as ./test).

$ strace ./test_perm 2
execve("./test_perm", ["./test_perm", "2"], [/* 8 vars */]) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x40005000
open("/lib/libc.so.0", O_RDONLY)        = 3
fstat(3, {st_mode=S_IFREG|0755, st_size=310348, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|0x4000000, -1, 0) = 0x40006000
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\260\256\0\0004\0\0\0"..., 4096) = 4096
mmap2(NULL, 360448, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4000e000
mmap2(0x4000e000, 303968, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x4000e000
mmap2(0x40060000, 4972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0x4a) = 0x40060000
mmap2(0x40062000, 15112, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40062000
close(3)                                = 0
munmap(0x40006000, 4096)                = 0
stat("/lib/ld-uClibc.so.0", {st_mode=S_IFREG|0755, st_size=21200, ...}) = 0
mprotect(0x40060000, 4096, PROT_READ)   = 0
mprotect(0x4000c000, 4096, PROT_READ)   = 0
ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(1, SNDCTL_TMR_TIMEBASE or TCGETS, {B38400 opost isig icanon echo ...}) = 0
geteuid32()                             = 1000
write(1, "saved privilege: 1000\n", 22saved privilege: 1000
) = 22
rt_sigaction(SIGQUIT, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [INT], SA_RESTART|0x4000000}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGCHLD, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, {SIG_DFL, [], 0}, 8) = 0
vfork(sh: cannot create /proc/sys/vm/drop_caches: Permission denied
)                                 = 1183
--- SIGCHLD (Child exited) @ 0 (0) ---
rt_sigaction(SIGQUIT, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [INT], SA_RESTART|0x4000000}, {SIG_IGN, [INT], SA_RESTART|0x4000000}, 8) = 0
wait4(1183, [{WIFEXITED(s) && WEXITSTATUS(s) == 2}], 0, NULL) = 1183
rt_sigaction(SIGQUIT, {SIG_DFL, [QUIT], SA_RESTART|0x4000000}, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [INT], SA_RESTART|0x4000000}, {SIG_IGN, [INT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGCHLD, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, 8) = 0
getuid32()                              = 1000
setresuid32(-1, 1000, -1)               = 0
getuid32()                              = 1000
geteuid32()                             = 1000
write(1, "dropped privileges 1000 1000\n", 29dropped privileges 1000 1000
) = 29
rt_sigaction(SIGQUIT, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, {SIG_DFL, [QUIT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [INT], SA_RESTART|0x4000000}, {SIG_DFL, [INT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGCHLD, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, 8) = 0
vfork(sh: cannot create /proc/sys/vm/drop_caches: Permission denied
)                                 = 1184
--- SIGCHLD (Child exited) @ 0 (0) ---
rt_sigaction(SIGQUIT, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [INT], SA_RESTART|0x4000000}, {SIG_IGN, [INT], SA_RESTART|0x4000000}, 8) = 0
wait4(1184, [{WIFEXITED(s) && WEXITSTATUS(s) == 2}], 0, NULL) = 1184
rt_sigaction(SIGQUIT, {SIG_DFL, [QUIT], SA_RESTART|0x4000000}, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [INT], SA_RESTART|0x4000000}, {SIG_IGN, [INT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGCHLD, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, 8) = 0
setuid32(1000)                          = 0
getuid32()                              = 1000
geteuid32()                             = 1000
write(1, "reacquired privileges 1000 1000\n", 32reacquired privileges 1000 1000
) = 32
rt_sigaction(SIGQUIT, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, {SIG_DFL, [QUIT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [INT], SA_RESTART|0x4000000}, {SIG_DFL, [INT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGCHLD, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, 8) = 0
vfork(sh: cannot create /proc/sys/vm/drop_caches: Permission denied
)                                 = 1185
--- SIGCHLD (Child exited) @ 0 (0) ---
rt_sigaction(SIGQUIT, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [INT], SA_RESTART|0x4000000}, {SIG_IGN, [INT], SA_RESTART|0x4000000}, 8) = 0
wait4(1185, [{WIFEXITED(s) && WEXITSTATUS(s) == 2}], 0, NULL) = 1185
rt_sigaction(SIGQUIT, {SIG_DFL, [QUIT], SA_RESTART|0x4000000}, {SIG_IGN, [QUIT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [INT], SA_RESTART|0x4000000}, {SIG_IGN, [INT], SA_RESTART|0x4000000}, 8) = 0
rt_sigaction(SIGCHLD, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, {SIG_DFL, [CHLD], SA_RESTART|0x4000000}, 8) = 0
exit(0)                                 = ?
  • What is next number in version 2.6? Unlike to 3.x and 4.x kernel series, it has a sence. In any case, 2.6.x kernel series is relatively old, are you sure you want to support it? Also, compiling old kernel under modern distro may introduce incopatibility between kernel core and libc. Can you perform the same operation (`echo 3 > /proc/sys/vm/drop_caches`) without your program (using `su` or `sudo /bin/sh -c `)? – Tsyvarev Sep 13 '16 at 06:49
  • @Tsyvarev, even *y* makes sense in *2.6.x.y* versioning. – 0andriy Sep 13 '16 at 06:55
  • It's possible that the output redirection '>' is causing problems via the system() function. Is the 'echo 3 > ...' the only command that does not work? If so, try entering privileged mode, actually opening the proc file via open(2) and write(2) to it. – kaiwan Sep 13 '16 at 10:29
  • 1
    @Tsyvarev the kernel is 2.6.28.9, and this is for an embedded platform with a custom distro. Definitely planning to move to newer kernels in the future, but right now, have to get this to work for existing project. Doing sudo of the same command works fine, which I do not understand. To address kaiwan's question, this is not the only command that does not work, it was just one example, I also cannot load drivers using system call system("insmod testdriver.ko") with this, basically anything that I am able to do as root, I cannot do with the program above. – wireless_freedom Sep 13 '16 at 17:00
  • in general, this statement: `exit(0);` is not correct for exiting after an error condition. That is because 0 is an indication of success. Suggest using (from stdlib.h) `exit( EXIT_FAILURE );` – user3629249 Sep 15 '16 at 12:53
  • the `switch()` statement is incorrectly written. There should be a `break;` statement at the end of each case. – user3629249 Sep 15 '16 at 13:09
  • device `/dev/myrandom` is not part of the OS, did you create that yourself? What permissions/properties does it have? – user3629249 Sep 15 '16 at 13:11
  • regarding this line: `system("dd if=/dev/zero of=/dev/myrandom");` the `/dev/zero` will only supply the requested number of NUL characters. Unfortunately, the command line is missing a `count=` parameter so this call might never exit. – user3629249 Sep 15 '16 at 13:25
  • To free slab objects and pagecache: `echo 3 > /proc/sys/vm/drop_caches` This file is not a means to control the growth of the various kernel caches (inodes, dentries, pagecache, etc...) These objects are automatically reclaimed by the kernel when memory is needed elsewhere on the system. Use of this file can cause performance problems. Since it discards cached objects, it may cost a significant amount of I/O and CPU to recreate the dropped objects, especially if they were under heavy use. Because of this, use outside of a testing or debugging environment is not recommended. – user3629249 Sep 15 '16 at 13:26

1 Answers1

0

Calling your program under strace caused the setuid bit to be ignored.

You tried to run setuid binaries with ruid == calling user's Id. This does not work very well; however this does not seem to be your main problem either.

Don't ever call system() from a setuid program else somebody does SHELL=/tmp/evil your_setuid_program and has root for breakfast.

I hesitate to even want to figure out what you're really doing wrong here that's causing the setuid bit to not work for you because you obviously have no idea how to write setuid binaries.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • well I have an application running in embedded Linux that needs to be able to do system() calls, but reduce privileges when system() calls are not needed, everything I've been reading says make it a setuid program, immediately reduce privileges at start of application, then reacquire root privileges only when necessary (when I need to make system() calls), and then drop privileges again, do you have a better suggestion on how to do this? – wireless_freedom Sep 13 '16 at 18:59
  • @wireless_freedom: Try to write a program that runs as root and does exactly what it needs to do without calling system() and then we can talk. If you can't do that there's just no point. For all of your examples other than insmod you shouldn't be starting any programs at all. – Joshua Sep 13 '16 at 19:49