1

Having the following pseudocode executed by a superuser, is it safe to assume that it is secure on Linux?

if fork() == 0:
   # drop privileges to an unprivileged user, let's say "nobody"
   # perform various tasks, start other processes as "nobody"
   # communicate with parent process through unsafe means, e.g. Python pickle

Now, Pickle is known to be susceptible to RCE if the data to be unpickled comes from an untrusted source. Although I assume my own code to be free of vulnerabilities, my concern is that the above code could still be vulnerable to privilege escalation through another process running as "nobody" that tampers with the memory of the unprivileged fork. If "nobody" could tamper with the memory of the process, it could potentially inject a pickled object which then leads to remote code execution in the parent process, i.e. as root user, which would be a privilege escalation. On the systems I tested this on, e.g. through writing to /proc/$pid/mem, I was able to tamper with the memory of another process only as superuser. However, I prefer to not just rely on a few tests on Linux machines where the behavior could depend on the distribution/configuration.

So my question is: Is this code secure? If so, where can I find a documentation guaranteeing that this behavior is universally secure? Alternatively, is there an argument that shows that a system would be inherently unsafe if it allowed an unprivileged user to tamper with all other processes running under their user id?

A question on how to implement secure child-to-parent communication in a setting where the child drops permissions has already been asked on Stackoverflow and since the accepted answer uses pickling under the hood, my question boils down to whether this approach is really secure.

3
  • you can't completely isolate a python process launched by yourself, within the same context as a malicious one, on the 'average' linux kernel, any more than you can punch yourself in the face and win... (eg. try strace next time you start python and see how much gets pulled in...) Q: do you have the luxury of signing or encrypting (aead) the pickled data?
    – brynk
    Commented Nov 9, 2020 at 1:57
  • I don't see how signing/authenticating could help as it would need to happen after the privilege drop and the keys would need to be accessible there. And to clarify, I don't assume the unprivileged Python process to be vulnerable or malicious itself. I just fear that another vulnerable process could be running as "nobody", which could in turn have the permissions to tamper with the memory of the unprivileged Python process after the privilege drop.
    – 1' OR 1 --
    Commented Nov 9, 2020 at 2:34
  • @1-or-1 you could validate a signature with only a public key, ie. your valid python process can produce the pickle, and in the future, you can validate the integrity before you trust it .. but can you do this for all .so or .pyc? as you already know, nothing will stop an adjacent process in the current context from potentially accessing memory, the risk here being that another process intercepts the secret signing key, and generates a trustworthy sig for a malicious xyz file (look at hidepid=2 viz. linux-dev.org/2012/09/hide-process-information-for-other-users)
    – brynk
    Commented Nov 10, 2020 at 1:31

1 Answer 1

1

If you start a process as yourself and you can't trust that other process, then yes, all your processes are vulnerable.

Root can generally access everything unless you have LinuxSE in which case root is not allowed to access other user's processes (at least by default), it can still see that they are running and send signals, though.

If you want to start another process in a more secure way, you have to

  1. fork()
  2. start a process that can change to that other user (i.e. suid bit set)
  3. change to that other user
  4. start the possibly non-secure process as that other user

systemd does a lot of that with services. Many services run as a user that's not root. In some cases, though, it starts as root (apache2) and change to another user later (www-data under Ubuntu) so the processes actually dealing with client connections are not running as root. But root is required to open port 80 and 443 (below 1024).

Actually systemd also has an option that defines how many child processes your service can start. It can be set to 0 in which case a simple system() call will fail every time (very strange when you don't know that the OS is capable of controlling that!) So there is definitely a potential security issue in starting any other process you're not 100% in control of. At the same time, this is very much the Unix way.

Another solution is to run everything in a docker so at least the main server can't be attacked directly. Your docker may get hacked, but you can always re-install and restart quickly (and work on a fix to prevent the hack from happening again, after you blocked that IP address in iptables).

8
  • "If you start a process as yourself and you can't trust that other process, then yes, all your processes are vulnerable." Do you have a source for this? I can see that this assumption may be helpful in some cases but does it apply in this specific case? I mean I am not doing things like reading files owned by "nobody" and executing their contents. I just want to know whether the process is sufficiently isolated in this specific case. Blocking IP addresses is not how I want to implement security.
    – 1' OR 1 --
    Commented Nov 9, 2020 at 3:00
  • 2
    Even the paradigm of starting processes as root and dropping privileges is becoming a thing of the past in Linux. With a POSIX capabilities-aware daemon, systemd or other process managers can grant e.g. CAP_NET_BIND to allow a webserver to bind to ports 80 and 443 without ever obtaining root privileges. Commented Nov 10, 2020 at 0:19
  • 1
    I have done further research and this is not possible in my case: "Permission to access this file is governed by a ptrace access mode PTRACE_MODE_ATTACH_FSCREDS check" (man7.org/linux/man-pages/man5/proc.5.html) The OS will "deny access if the target process 'dumpable' attribute has a value other than 1 [...] and the caller does not have the CAP_SYS_PTRACE capability" (man7.org/linux/man-pages/man2/ptrace.2.html) From man7.org/linux/man-pages/man2/prctl.2.html it follows that a process that has performed a privilege drop is not dumpable. So /proc/[pid]/mem is fine.
    – 1' OR 1 --
    Commented Nov 10, 2020 at 1:54
  • 1
    I wonder how gdb gains access to that process memory, though. Commented Nov 10, 2020 at 2:32
  • 1
    In most cases, processes debugged by gdb are dumpable because this attribute is 1 by default and 0 only under special circumstances, including privilege drops. (man7.org/linux/man-pages/man2/prctl.2.html) And according to man7.org/linux/man-pages/man2/ptrace.2.html, processes of the same user in the same user namespace are usually allowed to be ptraced. In my case, it is an exception that ptracing is not allowed because of the privilege drop.
    – 1' OR 1 --
    Commented Nov 10, 2020 at 11:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .