From 7fad754ff277e08c0c009a57cb3d65478e7b9338 Mon Sep 17 00:00:00 2001 From: Solderpunk Date: Sun, 19 Feb 2023 13:17:24 +0100 Subject: [PATCH] Drop privileges much more thoroughly, thanks nervuri! (see issue #16) --- main.go | 16 +------- security_dropprivs.go | 92 ++++++++++++++++++++++++++++++++++++++---- security_openbsd.go | 4 +- security_other_unix.go | 4 +- 4 files changed, 90 insertions(+), 26 deletions(-) diff --git a/main.go b/main.go index 2b1a71b..5c4e3c9 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,6 @@ import ( "log" "os" "os/signal" - "os/user" "strconv" "sync" "syscall" @@ -44,18 +43,7 @@ func main() { // If we are running as root, find the UID of the "nobody" user, before a // chroot() possibly stops seeing /etc/passwd - uid := os.Getuid() - nobody_uid := -1 - if uid == 0 { - nobody_user, err := user.Lookup(config.UnprivUsername) - if err != nil { - log.Fatal("Running as root but could not lookup UID for user " + config.UnprivUsername + ": " + err.Error()) - } - nobody_uid, err = strconv.Atoi(nobody_user.Uid) - if err != nil { - log.Fatal("Running as root but could not lookup UID for user " + config.UnprivUsername + ": " + err.Error()) - } - } + privInfo := getUserInfo(config) // Chroot, if asked if config.ChrootDir != "" { @@ -120,7 +108,7 @@ func main() { } // Apply security restrictions - enableSecurityRestrictions(config, nobody_uid, errorLog) + enableSecurityRestrictions(config, privInfo, errorLog) // Create TLS listener listener, err := tls.Listen("tcp", ":"+strconv.Itoa(config.Port), tlscfg) diff --git a/security_dropprivs.go b/security_dropprivs.go index 47a1d45..3b3a842 100644 --- a/security_dropprivs.go +++ b/security_dropprivs.go @@ -5,21 +5,97 @@ package main import ( "log" "os" + "os/user" "strconv" "syscall" ) -func DropPrivs(config Config, nobody_uid int, errorLog *log.Logger) { +type userInfo struct { + uid int + euid int + gid int + egid int + supp_groups []int + is_setuid bool + is_setgid bool + root_user bool + root_prim_group bool + root_supp_group bool + need_drop bool + unpriv_uid int + unpriv_gid int +} - // Get our real and effective UIDs - uid := os.Getuid() - euid := os.Geteuid() +func getUserInfo(config Config) userInfo { + var ui userInfo + ui.uid = os.Getuid() + ui.euid = os.Geteuid() + ui.gid = os.Getgid() + ui.egid = os.Getegid() + supp_groups, err := os.Getgroups() + if err != nil { + log.Fatal("Could not get supplementary groups: ", err.Error()) + } + ui.supp_groups = supp_groups + ui.unpriv_uid = -1 + ui.unpriv_gid = -1 - // Are we root or are we running as a setuid binary? - if uid == 0 || uid != euid { - err := syscall.Setuid(nobody_uid) + ui.is_setuid = ui.uid != ui.euid + ui.is_setgid = ui.gid != ui.egid + ui.root_user = ui.uid == 0 || ui.euid == 0 + ui.root_prim_group = ui.gid == 0 || ui.egid == 0 + for _, gid := range ui.supp_groups { + if gid == 0 { + ui.root_supp_group = true + break + } + } + ui.need_drop = ui.is_setuid || ui.is_setgid || ui.root_user || ui.root_prim_group || ui.root_supp_group + + if ui.need_drop { + nobody_user, err := user.Lookup(config.UnprivUsername) if err != nil { - errorLog.Println("Could not setuid to " + strconv.Itoa(uid) + ": " + err.Error()) + log.Fatal("Running as root but could not lookup UID for user " + config.UnprivUsername + ": " + err.Error()) + } + ui.unpriv_uid, err = strconv.Atoi(nobody_user.Uid) + ui.unpriv_gid, err = strconv.Atoi(nobody_user.Gid) + if err != nil { + log.Fatal("Running as root but could not lookup UID for user " + config.UnprivUsername + ": " + err.Error()) + } + } + + return ui +} +func DropPrivs(ui userInfo, errorLog *log.Logger) { + + // If we're already unprivileged, all good + if !ui.need_drop { + return + } + + // Drop supplementary groups + if ui.root_supp_group { + err := syscall.Setgroups([]int{}) + if err != nil { + errorLog.Println("Could not unset supplementary groups: " + err.Error()) + log.Fatal(err) + } + } + + // Setguid() + if ui.root_prim_group { + err := syscall.Setgid(ui.unpriv_gid) + if err != nil { + errorLog.Println("Could not setgid to " + strconv.Itoa(ui.unpriv_gid) + ": " + err.Error()) + log.Fatal(err) + } + } + + // Setuid() + if ui.root_user { + err := syscall.Setuid(ui.unpriv_uid) + if err != nil { + errorLog.Println("Could not setuid to " + strconv.Itoa(ui.unpriv_uid) + ": " + err.Error()) log.Fatal(err) } } diff --git a/security_openbsd.go b/security_openbsd.go index 8253893..647ce43 100644 --- a/security_openbsd.go +++ b/security_openbsd.go @@ -11,10 +11,10 @@ import ( // operations available to the molly brown executable. Please note that (S)CGI // processes that molly brown spawns or communicates with are unrestricted // and should pledge their own restrictions and unveil their own files. -func enableSecurityRestrictions(config Config, nobody_uid int, errorLog *log.Logger) { +func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger) { // Setuid to an unprivileged user - DropPrivs(config, nobody_uid, errorLog) + DropPrivs(ui, errorLog) // Unveil the configured document base as readable. log.Println("Unveiling \"" + config.DocBase + "\" as readable.") diff --git a/security_other_unix.go b/security_other_unix.go index 4567588..21f4aab 100644 --- a/security_other_unix.go +++ b/security_other_unix.go @@ -6,8 +6,8 @@ import ( "log" ) -func enableSecurityRestrictions(config Config, nobody_uid int, errorLog *log.Logger) { +func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger) { // Setuid to an unprivileged user - DropPrivs(config, nobody_uid, errorLog) + DropPrivs(ui, errorLog) }