Skip to content

os/exec: add way to influence the ambient capability set on Linux #19713

@stapelberg

Description

@stapelberg

Motivation

I’d like to use capabilities in order to run unprivileged programs under unprivileged user accounts with only precisely the capabilities the program needs. A concrete example is removing all privileges but CAP_SYS_TIME from an NTP client.

Example/details on what’s not working

Unfortunately, it seems like Go currently doesn’t provide a way to do this.

Here are 2 example programs:

  1. https://play.golang.org/p/_EN2RzKPE3 prints the current capabilities and tries to enable CAP_SYS_TIME
  2. https://play.golang.org/p/Yk6AKHyJGw sets the CAP_SYS_TIME capability in the inheritable and ambient capability set, then runs the first example program.

The output of program 2 is:

2017/03/25 17:58:00 ntp.go:17: caps = { effective="empty" permitted="empty" inheritable="sys_time" bounding="full" }
2017/03/25 17:58:00 ntp.go:22: could not apply caps: operation not permitted
2017/03/25 17:58:00 exit status 1

I would have expected the program to include sys_time in its effective capabilities set and not report an error.

Upon closer investigation, it turns out that setuid(2) clears the ambient capability set. Since forkAndExecInChild (src/syscall/exec_linux.go) applies cmd.SysProcAttr.Credential using setuid(2), the ambient capability set is cleared before my program is started.

Looking at systemd, you can see that they restore the ambient capability set after applying credentials: https://github.com/systemd/systemd/blob/1539a651a9d31c18273df917bbfe175ab3606025/src/core/execute.c#L2711-L2748

Proof of concept

Directly modifying the standard library to set the CAP_SYS_TIME capability as follows…

--- i/src/syscall/exec_linux.go
+++ w/src/syscall/exec_linux.go
@@ -227,6 +227,11 @@ func forkAndExecInChild(argv0 *byte, argv, envv []*byte, chroot, dir *byte, attr
                if err1 != 0 {
                        goto childerror
                }
+
+               _, _, err1 = RawSyscall(SYS_PRCTL, 47 /* PR_CAP_AMBIENT */, uintptr(2), uintptr(25) /* TIME */)
+               if err1 != 0 {
+                     goto childerror
+               }
        }
 
        // Chdir

…results in my test programs working:

2017/03/25 17:50:52 ntp.go:18: caps = { effective="sys_time" permitted="sys_time" inheritable="sys_time" bounding="full" }
2017/03/25 17:50:52 ntp.go:22: now: { effective="sys_time" permitted="sys_time" inheritable="sys_time" bounding="full" }

Question

Would a CL which adds an ambient capability struct member to syscall.SysProcAttr be accepted? If not, which other way would you recommend for starting processes with an ambient capability set?

Further reading

Metadata

Metadata

Assignees

No one assigned

    Labels

    FeatureRequestIssues asking for a new feature that does not need a proposal.FrozenDueToAgeNeedsFixThe path to resolution is known, but the work has not been done.

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions