process isolation in golang

729 views
Skip to first unread message

Carlo Contavalli

unread,
Dec 14, 2015, 4:49:08 PM12/14/15
to golan...@googlegroups.com
Hello,

Let's say you have something like this in C / C++:

main () {

   // Very beginning of main.
   int sockets[2];
   ... socketpair(... sockets);

   int process = fork();
   if (process != 0) {
     close(sockets[0]);

     chroot(...);
     dropAllRootPrivileges();
     RunWebServerForeverAndRequestActionsViaSocket(sockets[1]);
   } else {
     close(sockets[1]);

     keepSomeOfTheRootPrivileges(); // eg, CAP_SYS_ capabilities or similar.
     waitForRequestsOnSocketAndExecuteThem(sockets[0]);
   }
}

what would be the best implementation in golang?

It's a fairly common pattern to provide privilege separation: a more privileged but very simple process accepts and executes a very limited subset of operations based on input from pipe / socket, a much less privileged and more disposable process implements complex interactions with the user.

The issues I am running into are:

- fork() seems to be frowned upon. Easiest path seems to have two separate binaries? and ForkAndExec the second one? any alternative?

I'd like to avoid that if possible as it comes with installation headaches / fragility (eg, figure out path of second binary, make sure it is correct one ...).

- dropping posix capabilities ... although I have seen a few open source libraries out there for golang.

- ipc via file descriptor - channels are great, but will need some code to bridge channels and fd? need to look more into this.

Thank you for any suggestion,
Carlo 

Bakul Shah

unread,
Dec 14, 2015, 5:13:05 PM12/14/15
to Carlo Contavalli, golan...@googlegroups.com
On Mon, 14 Dec 2015 13:11:07 PST Carlo Contavalli <ccont...@gmail.com> wrote:
>
> Hello,
>
> Let's say you have something like this in C / C++:
>
> main () {
>
> // Very beginning of main.
> int sockets[2];
> ... socketpair(... sockets);
>
> int process = fork();
> if (process != 0) {
> close(sockets[0]);
>
> chroot(...);
> dropAllRootPrivileges();
> RunWebServerForeverAndRequestActionsViaSocket(sockets[1]);
> } else {
> close(sockets[1]);
>
> keepSomeOfTheRootPrivileges(); // eg, CAP_SYS_ capabilities or similar.
> waitForRequestsOnSocketAndExecuteThem(sockets[0]);
> }
> }
>
> what would be the best implementation in golang?

May be connect the webserver to the privileged server via
some sort of rpc or ipc since you can't have privilege
isolation in the same process?

> It's a fairly common pattern to provide privilege separation: a more
> privileged but very simple process accepts and executes a very limited
> subset of operations based on input from pipe / socket, a much less
> privileged and more disposable process implements complex interactions with
> the user.
>
> The issues I am running into are:
>
> - fork() seems to be frowned upon. Easiest path seems to have two separate
> binaries? and ForkAndExec the second one? any alternative?

> I'd like to avoid that if possible as it comes with installation headaches
> / fragility (eg, figure out path of second binary, make sure it is correct
> one ...).

You can fork-exec the same binary and run with different parameters.
os.Argv[0] should point to the program file name.

> - dropping posix capabilities ... although I have seen a few open source
> libraries out there for golang.
>
> - ipc via file descriptor - channels are great, but will need some code to
> bridge channels and fd? need to look more into this.

Channels are internal only. grpc/protobuf may do what you want
though not exactly lightweight (may be capnproto is
lighter weight but I haven't used it).

James Bardin

unread,
Dec 14, 2015, 5:36:54 PM12/14/15
to golang-nuts


On Monday, December 14, 2015 at 4:49:08 PM UTC-5, Carlo Contavalli wrote:

It's a fairly common pattern to provide privilege separation: a more privileged but very simple process accepts and executes a very limited subset of operations based on input from pipe / socket, a much less privileged and more disposable process implements complex interactions with the user.


Starting as root and dropping privs was never the safest way, but it was historically all that was available.
 

- fork() seems to be frowned upon. Easiest path seems to have two separate binaries? and ForkAndExec the second one? any alternative?


You can't reliably fork a multithreaded program, and all go programs are multithreaded from the start, which is why it's not available. 
 
I think the best way current is to start as an unprivileged user, and use the system tools to add the needed capabilities (setcap). 

Carlo Contavalli

unread,
Dec 14, 2015, 7:59:24 PM12/14/15
to Bakul Shah, golan...@googlegroups.com
Note that os.Argv[0] is part of what makes this approach fragile: if
the binary is in $PATH, for example, argv[0] may just be a binary name
who knows where it comes from. It's easy to search in the $PATH, but
I'd rather use something akin fork() if it was available.

Are there projects you can think of in go that do something similar to
what I need? How did they solve the problem?

By googling further... seems like I'm yet another victim of
https://github.com/golang/go/issues/227. Sounds like
https://github.com/sevlyar/go-daemon may be an approach.

Thank you all in any case,
Carlo

Bakul Shah

unread,
Dec 14, 2015, 8:57:54 PM12/14/15
to Carlo Contavalli, golan...@googlegroups.com
On Mon, 14 Dec 2015 16:58:56 PST Carlo Contavalli <ccont...@gmail.com> wrote:
>
> Note that os.Argv[0] is part of what makes this approach fragile: if
> the binary is in $PATH, for example, argv[0] may just be a binary name
> who knows where it comes from. It's easy to search in the $PATH, but
> I'd rather use something akin fork() if it was available.

argv[0] contains command name or path, if you specified one in
the command line. So why not specify a daemon's full path?

$(which my-daemon) args...

If you can't trust the shell or the filesystem where the executable
is fetched from, you may have bigger problems to worry about:-)

Manlio Perillo

unread,
Dec 15, 2015, 11:29:17 AM12/15/15
to golang-nuts
Il giorno lunedì 14 dicembre 2015 23:36:54 UTC+1, James Bardin ha scritto:

Starting as root and dropping privs was never the safest way, but it was historically all that was available.
 

Note, however, that the more modern seccomp linux syscall use a similar approach.

> [...]

Regards  Manlio

Konstantin Khomoutov

unread,
Dec 15, 2015, 12:54:39 PM12/15/15
to Carlo Contavalli, golan...@googlegroups.com
On Mon, 14 Dec 2015 13:11:07 -0800
Carlo Contavalli <ccont...@gmail.com> wrote:

[...]
> what would be the best implementation in golang?
>
> It's a fairly common pattern to provide privilege separation: a more
> privileged but very simple process accepts and executes a very limited
> subset of operations based on input from pipe / socket, a much less
> privileged and more disposable process implements complex
> interactions with the user.
>
> The issues I am running into are:
>
> - fork() seems to be frowned upon. Easiest path seems to have two
> separate binaries? and ForkAndExec the second one? any alternative?
>
> I'd like to avoid that if possible as it comes with installation
> headaches / fragility (eg, figure out path of second binary, make
> sure it is correct one ...).
>
> - dropping posix capabilities ... although I have seen a few open
> source libraries out there for golang.
>
> - ipc via file descriptor - channels are great, but will need some
> code to bridge channels and fd? need to look more into this.

I like re-executing of "/proc/self/exe" with lowered credentials
and with a custom option to switch on in a newly executed command.

Something like [1] (ripped off a real program).

This example could be made more elaborate. For instance, you can
attach a (Go) pipe to the stdin of the spawned process and write
something there expecting it to read this and decode. This can be used
for passing configuration bits to the copy running with lowered
privileges, which that would otherwise unable to access (say, a
configuration file with passwords which is root:root 0600). Or you can
also attach another pipe to the process's stdout and have a
bi-directional communication between them using any protocol you like
(encoding/json, net/textproto, protobuf etc).

1. http://play.golang.org/p/N7plnGf79z

Manlio Perillo

unread,
Dec 15, 2015, 1:58:55 PM12/15/15
to golang-nuts, ccont...@gmail.com
Il giorno martedì 15 dicembre 2015 18:54:39 UTC+1, Konstantin Khomoutov ha scritto:
> [...]

This example could be made more elaborate.  For instance, you can
attach a (Go) pipe to the stdin of the spawned process and write
something there expecting it to read this and decode.  This can be used
for passing configuration bits to the copy running with lowered
privileges, which that would otherwise unable to access (say, a
configuration file with passwords which is root:root 0600).  Or you can
also attach another pipe to the process's stdout and have a
bi-directional communication between them using any protocol you like
(encoding/json, net/textproto, protobuf etc).

1. http://play.golang.org/p/N7plnGf79z

Why not using os.IsPerm(err), in line 34? 


Regards  Manlio

Konstantin Khomoutov

unread,
Dec 15, 2015, 2:46:02 PM12/15/15
to Manlio Perillo, golang-nuts, ccont...@gmail.com
On Tue, 15 Dec 2015 10:58:55 -0800 (PST)
Manlio Perillo <manlio....@gmail.com> wrote:

> > This example could be made more elaborate. For instance, you can
> > attach a (Go) pipe to the stdin of the spawned process and write
> > something there expecting it to read this and decode. This can be
> > used for passing configuration bits to the copy running with
> > lowered privileges, which that would otherwise unable to access
> > (say, a configuration file with passwords which is root:root
> > 0600). Or you can also attach another pipe to the process's stdout
> > and have a bi-directional communication between them using any
> > protocol you like (encoding/json, net/textproto, protobuf etc).
> >
> > 1. http://play.golang.org/p/N7plnGf79z
> >
>
> Why not using os.IsPerm(err), in line 34?

I was not aware of its existence (and was using `syscall' anyway).

Thanks for the heads up!
Reply all
Reply to author
Forward
0 new messages