linux - Allow non-root process to bind to port 80 and 443?

09
2014-02
  • noloader

    Is it possible to tune a kernel parameter to allow a userland program to bind to port 80 and 443?

    The reason I ask is I think its foolish to allow a privileged process to open a socket and listen. Anything that opens a socket and listens is high risk, and high risk applications should not be running as root.

    I'd much rather try to figure out what unprivileged process is listening on port 80 rather than trying to remove malware that burrowed in with root privileges.

  • Answers
  • JdeBP

    Dale Hagglund is spot on. So I'm just going to say the same thing but in a different way, with some specifics and examples. ☺

    The right thing to do in the Unix and Linux worlds is:

    • to have a small, simple, easily auditable, program that runs as the superuser and binds the listening socket;
    • to have another small, simple, easily auditable, program that drops privileges, spawned by the first program;
    • to have the meat of the service, in a separate third program, run under a non-superuser account and chain loaded by the second program, expecting to simply inherit an open file descriptor for the socket.

    You have the wrong idea of where the high risk is. The high risk is in reading from the network and acting upon what is read not in the simple acts of opening a socket, binding it to a port, and calling listen(). It's the part of a service that does the actual communication that is the high risk. The parts that open, bind(), and listen(), and even (to an extent) the part that accepts(), are not the high risk and can be run under the aegis of the superuser. They don't use and act upon (with the exception of source IP addresses in the accept() case) data that are under the control of untrusted strangers over the network.

    There are many ways of doing this.

    inetd

    As Dale Hagglund says, the old "network superserver" inetd does this. The account under which the service process is run is one of the columns in inetd.conf. It doesn't separate the listening part and the dropping privileges part into two separate programs, small and easily auditable, but it does separate off the main service code into a separate program, exec()ed in a service process that it spawns with an open file descriptor for the socket.

    The difficulty of auditing isn't that much of a problem, as one only has to audit the one program. inetd's major problem is not auditing so much but is rather that it doesn't provide simple fine-grained runtime service control, compared to more recent tools.

    UCSPI-TCP and daemontools

    Daniel J. Bernstein's UCSPI-TCP and daemontools packages were designed to do this in conjunction. One can alternatively use Bruce Guenter's largely equivalent daemontools-encore toolset.

    The program to open the socket file descriptor and bind to the privileged local port is tcpserver, from UCSPI-TCP. It does both the listen() and the accept().

    tcpserver then spawns either a service program that drops root privileges itself (because the protocol being served involves starting out as the superuser and then "logging on", as is the case with, for example, an FTP or an SSH daemon) or setuidgid which is a self-contained small and easily auditable program that solely drops privileges and then chain loads to the service program proper (no part of which thus ever runs with superuser privileges, as is the case with, say, qmail-smtpd).

    A service run script would thus be for example (this one for dummyidentd for providing null IDENT service):

    #!/bin/sh -e
    exec 2>&1
    exec \
    tcpserver 0 113 \
    setuidgid nobody \
    dummyidentd.pl
    

    nosh

    My nosh package is designed to do this. It has a small setuidgid utility, just like the others. One slight difference is that it's usable with systemd-style "LISTEN_FDS" services as well as with UCSPI-TCP services, so the traditional tcpserver program is replaced by two separate programs: tcp-socket-listen and tcp-socket-accept.

    Again, single-purpose utilities spawn and chain load one another. One interesting quirk of the design is that one can drop superuser privileges after listen() but before even accept(). Here's a run script for qmail-smtpd that indeed does exactly that:

    #!/bin/nosh
    fdmove -c 2 1
    clearenv --keep-path --keep-locale
    envdir env/
    softlimit -m 70000000
    tcp-socket-listen --combine4and6 --backlog 2 ::0 smtp
    setuidgid qmaild
    sh -c 'exec \
    tcp-socket-accept -v -l "${LOCAL:-0}" -c "${MAXSMTPD:-1}" \
    ucspi-socket-rules-check \
    qmail-smtpd \
    '
    

    The programs that run under the aegis of the superuser are the small service-agnostic chain-loading tools fdmove, clearenv, envdir, softlimit, tcp-socket-listen, and setuidgid. By the point that sh is started, the socket is open and bound to the smtp port, and the process no longer has superuser privileges.

    s6, s6-networking, and execline

    Laurent Bercot's s6 and s6-networking packages were designed to do this in conjunction. The commands are structurally very similar to those of daemontools and UCSPI-TCP.

    run scripts would be much the same, except for the substitution of s6-tcpserver for tcpserver and s6-setuidgid for setuidgid. However, one might also choose to make use of M. Bercot's execline toolset at the same time.

    Here's an example of an FTP service, lightly modified from Wayne Marshall's original, that uses execline, s6, s6-networking, and the FTP server program from publicfile:

    #!/command/execlineb -PW
    multisubstitute {
        define CONLIMIT 41
        define FTP_ARCHIVE "/var/public/ftp"
    }
    fdmove -c 2 1
    s6-envuidgid pubftp 
    s6-softlimit -o25 -d250000 
    s6-tcpserver -vDRH -l0 -b50 -c ${CONLIMIT} -B '220 Features: a p .' 0 21 
    ftpd ${FTP_ARCHIVE}
    

    ipsvd

    Gerrit Pape's ipsvd is another toolset that runs along the same lines as ucspi-tcp and s6-networking. The tools are chpst and tcpsvd this time, but they do the same thing, and the high risk code that does the reading, processing, and writing of things sent over the network by untrusted clients is still in a separate program.

    Here's M. Pape's example of running fnord in a run script:

    #!/bin/sh
    exec 2>&1
    cd /public/10.0.5.4
    exec \
    chpst -m300000 -Uwwwuser \
    tcpsvd -v 10.0.5.4 443 sslio -v -unobody -//etc/fnord/jail -C./cert.pem \
    fnord
    

    systemd

    systemd, the new service supervision and init system that can be found in some Linux distributions, is intended to do what inetd can do. However, it doesn't use a suite of small self-contained programs. One has to audit systemd in its entirety, unfortunately.

    With systemd one creates configuration files to define a socket that systemd listens on, and a service that systemd starts. The service "unit" file has settings that allow one a great deal of control over the service process, including what user it runs as.

    With that user set to be a non-superuser, systemd does all of the work of opening the socket, binding it to a port, and calling listen() (and, if required, accept()) in process #1 as the superuser, and the service process that it spawns runs without superuser privileges.

  • Dale Hagglund

    Your instincts are entirely correct: it's a bad idea to have a large complex program run as root, because their complexity makes them hard to trust.

    But, it's also a bad idea to allow regular users to bind to privileged ports, because such ports usually represent important system services.

    The standard approach to resolving this apparent contradiction is privilege separation. The basic idea is to separate your program into two (or more) parts, each of which does a well defined piece of the overall application, and which communicate by simple limited interfaces.

    In the example you give, you want to separate your program into two pieces. One that runs as root and opens and binds to the privileged socket, and then hands it off somehow to the other part, which runs as a regular user.

    These two main ways to achieve this separation.

    1. A single program that starts as root. The very first thing it does is create the necessary socket, in as simple and limited a way as possible. Then, it drops privileges, that is, it converts itself into a regular user mode process, and does all other work. Dropping privileges correctly is tricky, so please take the time to study the right way to do it.

    2. A pair of programs that communicate over a socket pair created by a parent process. A non-privileged driver program receives initial arguments and perhaps does some basic argument validation. It creates pair of connected sockets via socketpair(), and then forks and execs two other programs that will do the real work, and communicate via the socket pair. One of these is privileged and will create the server socket, and any other privileged operations, and the other will do the more complex and therefore less trustworthy application execution.

    [1] http://en.m.wikipedia.org/wiki/Privilege_separation


  • Related Question

    linux - Change EUID of running process
  • jackhab

    On Linux, how can I change EUID of running process from command line (provided I have root access)?


  • Related Answers
  • quack quixote

    If the process is running with root-privileges, you could attach gdb to the process and call seteuid from within that process.

    Example:

    [root@user-desktop ~]# id
    uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) context=user_u:system_r:unconfined_t
    
    [root@user-desktop ~]# gdb /bin/bash $$
    GNU gdb Fedora (6.8-27.el5)
    # cut copyright & license statements
    This GDB was configured as "x86_64-redhat-linux-gnu"...
    # cut some initialization output    
    0x00000036b0a99335 in waitpid () from /lib64/libc.so.6
    (gdb) call seteuid(500)
    $1 = 0 
    (gdb) quit
    The program is running.  Quit anyway (and detach it)? (y or n) y
    Detaching from program: /bin/bash, process 29017
    
    [root@user-desktop ~]# id
    uid=0(root) gid=0(root) euid=500(user) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) context=user_u:system_r:unconfined_t
    
  • Jonathan Leffler

    If you are talking about a process changing its own EUID, there are a bunch of ways to do that.

    • setuid() - as a side-effect sets EUID when used by a process with EUID of 0
    • seteuid()
    • setreuid()

    Depending on the effective UID of the program, and whether there is a saved UID, you may be able to switch between two EUID values in a non-root program. With a root privileged program, you have to be careful - you have to decide whether the change should be irreversible, and use the correct function for the job. (Using setuid() as root is irreversible.)

    If you are trying to change a process that's already running from a separate process, then there is no standard way to do it - and I'm not sure there are many non-standard ways, either. You might be able to dink some information in /dev/kmem, but the expression 'thin ice' springs to mind.

  • pbr

    There's no way to do this "from the commandline" to just any running process.

    I can say that with some sureness; the only "maybe" was /proc and I poked around in there (literally and via google) and ran into a dead-end regarding anything in /proc allowing for changing the EUID. You can LEARN what the UID and GID settings are in /proc/{pid}/status - but you can't change them using anything in /proc, at least as far as I can tell.

    But it's easy enough to make something like that work -- a way to change the EUID of a process, from the commandline -- if you control the source code of the process you want to change. You can implement a signal handler for say SIGUSR1 and have the process change its own EUID however you need when it receives that signal. Then you would simply send the process that SIGUSR1 signal, via "kill" ...from the commandline, as you've asked... and it would change its EUID for you.

    This might not be what you were thinking of, but... it's an answer to your question of how to do it... and it's the only answer I can think of.