Avoid use of log.Fatal() or os.Exit() in main so defers are guaranteed to run.

This commit is contained in:
Solderpunk 2023-02-19 14:40:54 +01:00
parent 7fad754ff2
commit 072669a167
6 changed files with 62 additions and 30 deletions

37
main.go
View File

@ -41,15 +41,26 @@ func main() {
log.Fatal(err)
}
// Run server and exit
os.Exit(do_main(config))
}
func do_main(config Config) int {
// If we are running as root, find the UID of the "nobody" user, before a
// chroot() possibly stops seeing /etc/passwd
privInfo := getUserInfo(config)
privInfo, err := getUserInfo(config)
if err != nil {
errorLog.Println("Exiting due to failure to apply security restrictions.")
return 1
}
// Chroot, if asked
if config.ChrootDir != "" {
err := syscall.Chroot(config.ChrootDir)
if err != nil {
log.Fatal("Could not chroot to " + config.ChrootDir + ": " + err.Error())
log.Println("Could not chroot to " + config.ChrootDir + ": " + err.Error())
return 1
}
}
@ -60,7 +71,8 @@ func main() {
} else {
errorLogFile, err = os.OpenFile(config.ErrorLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
log.Println(err)
return 1
}
defer errorLogFile.Close()
}
@ -73,7 +85,8 @@ func main() {
accessLogFile, err = os.OpenFile(config.AccessLog, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
errorLog.Println("Error opening access log file: " + err.Error())
log.Fatal(err)
log.Println(err)
return 1
}
defer accessLogFile.Close()
}
@ -83,16 +96,16 @@ func main() {
info, err := os.Stat(config.KeyPath)
if err != nil {
errorLog.Println("Error opening TLS key file: " + err.Error())
log.Fatal(err)
return 1
}
if uint64(info.Mode().Perm())&0444 == 0444 {
errorLog.Println("Refusing to use world-readable TLS key file " + config.KeyPath)
os.Exit(0)
return 1
}
cert, err := tls.LoadX509KeyPair(config.CertPath, config.KeyPath)
if err != nil {
errorLog.Println("Error loading TLS keypair: " + err.Error())
log.Fatal(err)
return 1
}
tlscfg := &tls.Config{
Certificates: []tls.Certificate{cert},
@ -108,13 +121,17 @@ func main() {
}
// Apply security restrictions
enableSecurityRestrictions(config, privInfo, errorLog)
err = enableSecurityRestrictions(config, privInfo, errorLog)
if err != nil {
errorLog.Println("Exiting due to failure to apply security restrictions.")
return 1
}
// Create TLS listener
listener, err := tls.Listen("tcp", ":"+strconv.Itoa(config.Port), tlscfg)
if err != nil {
errorLog.Println("Error creating TLS listener: " + err.Error())
log.Fatal(err)
return 1
}
defer listener.Close()
@ -164,4 +181,6 @@ func main() {
wg.Wait()
errorLog.Println("Exiting.")
// Exit successfully
return 0
}

View File

@ -10,5 +10,5 @@ import (
// This is intended to be called immediately prior to accepting client
// connections and may be used to establish a security "jail" for the molly
// brown executable.
func enableSecurityRestrictions(config Config, errorLog *log.Logger) {
func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger) error {
}

View File

@ -26,7 +26,7 @@ type userInfo struct {
unpriv_gid int
}
func getUserInfo(config Config) userInfo {
func getUserInfo(config Config) (userInfo, error) {
var ui userInfo
ui.uid = os.Getuid()
ui.euid = os.Geteuid()
@ -34,7 +34,8 @@ func getUserInfo(config Config) userInfo {
ui.egid = os.Getegid()
supp_groups, err := os.Getgroups()
if err != nil {
log.Fatal("Could not get supplementary groups: ", err.Error())
log.Println("Could not get supplementary groups: ", err.Error())
return ui, err
}
ui.supp_groups = supp_groups
ui.unpriv_uid = -1
@ -55,22 +56,24 @@ func getUserInfo(config Config) userInfo {
if ui.need_drop {
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())
log.Println("Running as root but could not lookup UID for user " + config.UnprivUsername + ": " + err.Error())
return ui, err
}
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())
log.Println("Running as root but could not lookup UID for user " + config.UnprivUsername + ": " + err.Error())
return ui, err
}
}
return ui
return ui, nil
}
func DropPrivs(ui userInfo, errorLog *log.Logger) {
func DropPrivs(ui userInfo, errorLog *log.Logger) error {
// If we're already unprivileged, all good
if !ui.need_drop {
return
return nil
}
// Drop supplementary groups
@ -78,7 +81,7 @@ func DropPrivs(ui userInfo, errorLog *log.Logger) {
err := syscall.Setgroups([]int{})
if err != nil {
errorLog.Println("Could not unset supplementary groups: " + err.Error())
log.Fatal(err)
return err
}
}
@ -87,7 +90,7 @@ func DropPrivs(ui userInfo, errorLog *log.Logger) {
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)
return err
}
}
@ -96,8 +99,9 @@ func DropPrivs(ui userInfo, errorLog *log.Logger) {
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)
return err
}
}
return nil
}

View File

@ -7,7 +7,7 @@ import (
"os"
)
func enableSecurityRestrictions(config Config, errorLog *log.Logger) {
func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger) error {
// Prior to Go 1.6, setuid did not work reliably on Linux
// So, absolutely refuse to run as root
@ -16,6 +16,8 @@ func enableSecurityRestrictions(config Config, errorLog *log.Logger) {
if uid == 0 || euid == 0 {
setuid_err := "Refusing to run with root privileges when setuid() will not work!"
errorLog.Println(setuid_err)
log.Fatal(setuid_err)
return error.New(setuid_err)
}
return nil
}

View File

@ -11,17 +11,20 @@ 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, ui userInfo, errorLog *log.Logger) {
func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger) error {
// Setuid to an unprivileged user
DropPrivs(ui, errorLog)
err := DropPrivs(ui, errorLog)
if err != nil {
return err
}
// Unveil the configured document base as readable.
log.Println("Unveiling \"" + config.DocBase + "\" as readable.")
err := unix.Unveil(config.DocBase, "r")
if err != nil {
errorLog.Println("Could not unveil DocBase: " + err.Error())
log.Fatal(err)
return err
}
// Unveil cgi path globs as executable.
@ -32,7 +35,7 @@ func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger
err = unix.Unveil(cgiGlobbedPath, "rx")
if err != nil {
errorLog.Println("Could not unveil CGIPaths: " + err.Error())
log.Fatal(err)
return err
}
}
}
@ -41,6 +44,9 @@ func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger
for _, scgiSocket := range config.SCGIPaths {
log.Println("Unveiling \"" + scgiSocket + "\" as read/write.")
err = unix.Unveil(scgiSocket, "rw")
if err != nil {
return err
}
}
// Finalize the unveil list.
@ -48,7 +54,7 @@ func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger
err = unix.UnveilBlock()
if err != nil {
errorLog.Println("Could not block unveil: " + err.Error())
log.Fatal(err)
return err
}
// Pledge to only use stdio, inet, and rpath syscalls.
@ -64,6 +70,6 @@ func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger
err = unix.PledgePromises(promises)
if err != nil {
errorLog.Println("Could not pledge: " + err.Error())
log.Fatal(err)
return err
}
}

View File

@ -6,8 +6,9 @@ import (
"log"
)
func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger) {
func enableSecurityRestrictions(config Config, ui userInfo, errorLog *log.Logger) error {
// Setuid to an unprivileged user
DropPrivs(ui, errorLog)
return DropPrivs(ui, errorLog)
}