1

I'd like to run a service as a non-privileged user, but it needs to bind to a system port number (i.e. less than 1024), so I give it setcap 'cap_net_bind_service=+ep' <path for service>, all good.

Problem is, on startup, the service reads environment vars and for some reason it can't do that when it has cap_net_bind_service. So, with two copies of the executable, one with cap_net_bind_service, one without, only the one without can read environment vars.

It's as though there's a default set of capabilities that allows reading env vars, but the exe loses that capability when I give it cap_net_bind_service. Is that right, or is something else going on? What additional capability might I need to give to the service so that it can read env vars? There's nothing in capability.h that jumps out as being "allow env var reading"?

code9016
  • 21
  • 4
  • Are you sure that is the problem? That a process cannot read its own environment doesn't make any sense to me. That is just data in its own address space. I just created a copy of a binary, executed your `setcap` command on the copy and compared the environments of both processes: No difference, it seems. – Hauke Laging May 24 '20 at 03:49
  • It's certainly what it looks like. The binary in question comes from ocaml source, so I don't know if whatever library it's using to access the environment variables ([sys getenv](https://caml.inria.fr/pub/docs/manual-ocaml/libref/Sys.html)) is using some wacky method that needs additional capabilities to do it? – code9016 May 24 '20 at 05:32
  • I wonder if the setcap is actually [removing LD_ variables](https://stackoverflow.com/questions/9843178/linux-capabilities-setcap-seems-to-disable-ld-library-path)? – code9016 May 24 '20 at 05:43
  • I can't see anything obviously significant in strace output between the two binaries, but running with [LD_DEBUG=all](http://man7.org/linux/man-pages/man8/ld.so.8.html) produces lots of linking output for the binary without the setcap and nothing at all for binary with the setcap, until I create /etc/suid-debug, which confirms it is running in secure-execution mode. I can't see any significant difference in the LD_DEBUG output either, so there must be something else about secure-execution that's stopping it seeing the environment variables? – code9016 May 24 '20 at 07:35
  • 1
    I bet it's [secure_getenv](http://man7.org/linux/man-pages/man3/secure_getenv.3.html) - "... returns NULL in cases where "secure execution" is required." - setcap is definitely causing it to run in secure execution mode. I bet ocaml sys getenv is being mapped through to glibc secure_getenv. – code9016 May 24 '20 at 08:35
  • 1
    And there it is: ["local privilege escalation issue with ocaml binaries"](https://github.com/ocaml/ocaml/issues/7557) - ocaml does now use secure_getenv. – code9016 May 24 '20 at 10:19

1 Answers1

1

I got to the bottom of it. In brief, the binary is using secure_getenv to access environment variables.

This returns null, instead of accessing the variables, when the binary is run in "secure execution" mode (AT_SECURE=1). Having any capability set, causes it to run in this mode.

Confirm that the binary is using secure_getenv using readelf:

readelf -a <path for service> | grep getenv
00000040ac28  004b00000007 R_X86_64_JUMP_SLO 0000000000000000 secure_getenv@GLIBC_2.17 + 0
00000040ad90  007a00000007 R_X86_64_JUMP_SLO 0000000000000000 getenv@GLIBC_2.2.5 + 0
    75: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND secure_getenv@GLIBC_2.17 (6)
   122: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND getenv@GLIBC_2.2.5 (2)

Confirm that it's running in secure execution mode with environment variables (how ironic!) LD_DEBUG=all and/or LD_SHOW_AUXV (see man ld.so).

If it isn't then LD_SHOW_AUXV produces output with AT_SECURE set to 0. There is no output from LD_SHOW_AUXV when it is running in secure execution mode.

Normally, there is/isn't output from LD_DEBUG too when it is/isn't running in secure execution mode. However, if /etc/suid-debug is present (empty file, create with touch), then LD_DEBUG will produce output when running in secure execution mode.

See man getauxval for more information about AT_SECURE and secure execution mode.

code9016
  • 21
  • 4
  • I am not familiar with `AT_SECURE` but the explanation seems not to include the case that a process got capabilities by being run without its own file caps, from a process with those capabilities. And that makes sense: A privileged process can and should make sure that it calls other processes in a secure way, especially when caps are to be inherited. So maybe it works if you create a tiny wrapper binary which gets the file caps and just makes an execve to your application. – Hauke Laging May 24 '20 at 14:27