From 8666e4461c9612e1432f05af6e0abfd716adca36 Mon Sep 17 00:00:00 2001 From: ston1th Date: Mon, 5 Sep 2016 22:15:15 +0200 Subject: [PATCH] initial commit --- .gitignore | 1 + LICENSE | 24 ++++++++ README.md | 3 + example/main.go | 31 ++++++++++ godrop.go | 160 ++++++++++++++++++++++++++++++++++++++++++++++++ godrop_test.go | 23 +++++++ 6 files changed, 242 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 example/main.go create mode 100644 godrop.go create mode 100644 godrop_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..528cd5b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +example/example diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9e52d9b --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +Copyright (C) 2016 Marius Schellenberger +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * The names of the authors and/or contributors may not be used to + endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ston1th BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6410577 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# godrop - drop privileges + +Godrop is a simple library to drop privileges on linux maschines. diff --git a/example/main.go b/example/main.go new file mode 100644 index 0000000..2e4f848 --- /dev/null +++ b/example/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "net" + "net/http" + "os" + + "git.giftfish.de/ston1th/godrop" +) + +func main() { + cfg := godrop.Config{ + User: "nobody", + Group: "nobody", + } + err := godrop.Drop(cfg, func() (net.Listener, error) { return net.Listen("tcp", ":80") }) + if err != nil { + fmt.Println(err) + } + + l, err := godrop.GetListener() + if err != nil { + fmt.Println("Failed to listen on FD 3:", err) + os.Exit(1) + } + + http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "pid %d\nuid %d\ngid %d", os.Getpid(), os.Getuid(), os.Getgid()) + })) +} diff --git a/godrop.go b/godrop.go new file mode 100644 index 0000000..d9a4531 --- /dev/null +++ b/godrop.go @@ -0,0 +1,160 @@ +package godrop + +import ( + "errors" + "io/ioutil" + "net" + "os" + "os/exec" + "regexp" + "strconv" + "syscall" +) + +func readFile(name, file string) (id int, err error) { + id = -1 + if name == "" { + return + } + f, err := os.Open(file) + if err != nil { + return + } + b, err := ioutil.ReadAll(f) + if err != nil { + return + } + r := regexp.MustCompile(name + ":.*:(\\d+)") + m := r.FindAllStringSubmatch(string(b), 1) + if len(m) == 1 { + if len(m[0]) == 2 { + return strconv.Atoi(m[0][1]) + } + } + return +} + +// UID of user +func UID(user string) (int, error) { + return readFile(user, "/etc/passwd") +} + +// GID of group +func GID(group string) (int, error) { + return readFile(group, "/etc/group") +} + +type Config struct { + User string + Group string + Chroot string +} + +// Drop will spawn a new process, hand over the listening socket file descriptor and terminate itself after +func Drop(c Config, f func() (net.Listener, error)) error { + uid, err := UID(c.User) + if err != nil { + return err + } + gid, err := GID(c.Group) + if err != nil { + return err + } + switch os.Getuid() { + case 0: + ln, err := f() + if err != nil { + return err + } + + l, ok := ln.(*net.TCPListener) + if !ok { + return errors.New("godrop: interface conversion failed") + } + f, err := l.File() + if err != nil { + return err + } + + cmd := exec.Command(os.Args[0], os.Args[1:]...) + cmd.ExtraFiles = []*os.File{f} + cmd.SysProcAttr = &syscall.SysProcAttr{ + Chroot: c.Chroot, + Credential: &syscall.Credential{ + Uid: uint32(uid), + Gid: uint32(gid), + }, + Setsid: true, + } + + if err := cmd.Start(); err != nil { + return err + } + + cmd.Process.Release() + os.Exit(0) + case uid: + return nil + } + return errors.New("godrop: droping priviledges failed") +} + +// MultiDrop will spawn a new process, hand over the all listening sockets and terminate itself after +func MultiDrop(c Config, f func() ([]net.Listener, error)) error { + uid, err := UID(c.User) + if err != nil { + return err + } + gid, err := GID(c.Group) + if err != nil { + return err + } + switch os.Getuid() { + case 0: + cmd := exec.Command(os.Args[0], os.Args[1:]...) + ln, err := f() + if err != nil { + return err + } + for _, v := range ln { + l, ok := v.(*net.TCPListener) + if !ok { + return errors.New("godrop: interface conversion failed") + } + f, err := l.File() + if err != nil { + return err + } + cmd.ExtraFiles = append(cmd.ExtraFiles, f) + } + + cmd.SysProcAttr = &syscall.SysProcAttr{ + Chroot: c.Chroot, + Credential: &syscall.Credential{ + Uid: uint32(uid), + Gid: uint32(gid), + }, + Setsid: true, + } + + if err := cmd.Start(); err != nil { + return err + } + + cmd.Process.Release() + os.Exit(0) + case uid: + return nil + } + return errors.New("godrop: droping priviledges failed") +} + +// GetListener returns the listener socket of file descriptor 3 +func GetListener() (net.Listener, error) { + return net.FileListener(os.NewFile(3, "[socket]")) +} + +// GetListenerFd returns the listener socket of the given file descriptor +func GetListenerFd(fd int) (net.Listener, error) { + return net.FileListener(os.NewFile(uintptr(fd), "[socket]")) +} diff --git a/godrop_test.go b/godrop_test.go new file mode 100644 index 0000000..38b629d --- /dev/null +++ b/godrop_test.go @@ -0,0 +1,23 @@ +package godrop + +import "testing" + +func TestUID(t *testing.T) { + uid, err := UID("root") + if err != nil { + t.Error(err) + } + if uid != 0 { + t.Error("expected uid '0' got", uid) + } +} + +func TestGID(t *testing.T) { + gid, err := GID("root") + if err != nil { + t.Error(err) + } + if gid != 0 { + t.Error("expected gid '0' got", gid) + } +}