Search this site


Metadata

Articles

Projects

Presentations

Setuid bind(2) and switch!

Got a program that can't setuid but needs to listen on a priviledged port? I was hacking around with Linux's capabilities(7) tonight and came up empty trying to allow a non-priviledged user to bind to port 80 without having to start as root - after all, not everything is capable of setuid on startup, like many java programs.

Speaking of java, if you do setuid, the java hotspot monitor file thing (/tmp/hsperfdata_<user>/<pid>) is in the original user's directory, not the setuid'd user, so you can't jstack reliably. I might be PEBCAKing it, but this is the behavior I observe. Unless we can fix that, and my java-fu is weak, we'll have to find a workaround. One workaround is to run a proxy or firewall redirector that forwards the privileged port to the real port.

Linux supports this process setting that allows a special non-root process to listen on privileged ports, but I can't get it working. I gave up trying to use libcap's sucap, execcap, and setpcaps tools trying to allow nonroot processes to bind to lower ports. Let's hack it with LD_PRELOAD and execve(2).

The trick is creating a socket and binding on the correct port, then sharing that socket with your process. This comes with two steps, the socket creation, and sharing.

The creation is easy, for simplicity, I used ruby. The sharing requires LD_PRELOAD - we'll override bind(2) and have it dup our existing socket to whatever socket bind(2) is being called with. The sharing is done using environment variables to share the file descriptor number and the port number.

The end result looks like this, where we can now force programs that try to bind on our specified port to use our pre-created socket instead.

# id
uid=0(root) gid=0(root) groups=0(root)
# ./bindandswitch.rb
Usage: bindandswitch.rb [host]:port user group command [arg1 ...]
# ./bindandswitch.rb localhost:80 jls jls nc -l -p 80
setgid: 1000 (jls)
setuid: 1000 (jls)
Exec: ["nc", "-l", "-p", "80"]


# -- now in another terminal --
% lsof -i :80 | grep LISTEN 
 nc        27092  jls    4u  IPv4 8100070       TCP localhost:www (LISTEN)
See, now we have given 'nc' the ability to bind to privileged ports as non-root (user 'jls' above). Sweet!

The ruby code takes care of creating the socket and doing setuid (I was lazy and didn't want to write the C code). I also have a very short C library that simply dup2()'s the original bound socket when bind(2) is called by the new process on our specific port.

bind(2) calls for other ports than the one specified in the command line are handled normally. For example, this fails:

# ./bindandswitch.rb localhost:33 jls jls nc -l -p 80
setgid: 1000 (jls)
setuid: 1000 (jls)
Exec: ["nc", "-l", "-p", "80"]
Can't grab 0.0.0.0:80 with bind : Permission denied
The reason for this investigation was really for seeing how we could allow non-root java processes to bind to port 80. Maybe I'll use this at work if it behaves well.

Code:

XSendEvent + LD_PRELOAD == win

As far as feature requests come, for xdotool, one of the more common ones is to have the ability to send key or mouse events to a specific window, not just the active one. XTEST (what xdotool uses for key/mouse currently) doesn't let you specify a window to send events. XSendEvent(3) lets you send hand-crafted events to a specific window, but most applications ignore these sent events.

The XEvent struct has a member 'send_event' which is true if the event came from an XSendEvent call and false otherwise. Programs like firefox and xterm (by default) ignore many events that have 'send_event' set to true.

Enter LD_PRELOAD.

Writing a custom shared library that overrides the default XNextEvent and XPeekEvent functions allows us to force 'send_event' to always be false, so an application with this library loaded will happily handle keyboard/mouse events generated with XSendEvent. I already have a helpful project that lets me write such a shared library: liboverride.

#include <stdio.h>
#include <X11/Xlib.h>

void hack_send_event(XEvent *ev) {
  switch (ev->type) {
    case KeyPress: case KeyRelease: case ButtonPress: case ButtonRelease:
      ev->xany.send_event = False;
      break;
  }
}

override(`XNextEvent', `
  {
    real_func(display, event_return);
    hack_send_event(event_return);
    return;
  }
')
This small bit of liboverride code will give me a shared library I can preload with LD_PRELOAD. Doing so will ensure that send_event is false for any key or mouse button events.

Works well. Now that we have a reliable way to allow XSendEvent I think it's worth putting this into xdotool.