Is SetGID/SetUID on a Go[lang] binary safe?
Asked Answered
G

1

6

I've written a simple program using YAML and the MySQL drivers with the intention of providing a simple utility to update a database without exposing the username and password credentials to the user executing the program.

(I'm well aware that I could also write this in Python or some other scripting language and manage the permissions delegations using but I'd like to try a different approach here, for my own edification).

After building the program I've used chgrp sys dbcreds.yaml && chmod 0640 dbcreds.yaml and chgrp sys ./myprog && chmod g+s ./myprog (as root) ... and everything seems to work. (I also tested that access was denied, as it should be, prior to the setGID step).

I also tested strace and that results in permission denied (as it should be). (For fun I also ran ltrace -S on it; this is under Linux. As expected I did not see many normal libc function calls ... through I am surprised to have seen a few pthread_....() and one malloc() calls in that listing. I guess the GO runtime does link to some system library functions after all).

My question: is this safe? Is there any known way to cause a Go program, such as this (below) to core dump or expose its memory after it has read these private credentials? Is there a way to drop my SGID privs after I've read my credentials? Are there any examples of SUID/SGID exploits on Go binaries? Is there a better way to do this? Is there a way to proactive prevent core dumps or ensure that sensitive data (credentials) would not be in core dumps?

One other note: I find the gopkg.in/yaml.v2 semantics to be a bit disconcerting. In my YAML file I have something like:

---
user me
pw mypassword

But in my code I have to use User and Pw (capitalized) rather than using lower case as I would have expected. I presume this is an implementation decision by the authors of Goyaml. Is that so?

#!go
package main

import (
    "fmt"
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "gopkg.in/yaml.v2"
    "io/ioutil"
    "os"
    "strconv"
)

type Creds struct {
    User string
    Pw   string
}

func main() {

    filename := "./dbcreds.yaml"
    var creds Creds
    conf, err := ioutil.ReadFile(filename)
    if err != nil {
        panic(err)
    }
    err = yaml.Unmarshal(conf, &creds)
    if err != nil {
        panic(err)
    }

    var arg1 int
    arg1, err = strconv.Atoi(os.Args[1])
    if err != nil {
        panic(err.Error()) // Just for example purpose. You should use proper error handling instead of panic
    }

    fmt.Println("arg1: ", arg1, "\n")
    dsn := fmt.Sprintf("%s:%s@/mydatabase", creds.User, creds.Pw)
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        panic(err.Error())
    }
    defer db.Close()

    err = db.Ping()
    if err != nil {
        panic(err.Error())
    }

    stmtOut, err := db.Prepare("SELECT quant FROM c WHERE id >= ?")
    if err != nil {
        panic(err.Error())
    }
    defer stmtOut.Close()

    rows, err := stmtOut.Query(arg1)
    if err != nil {
        panic(err.Error())
    }
    defer rows.Close()

    for rows.Next() {
        var quant int
        err = rows.Scan(&quant)
        if err != nil {
            panic(err.Error())
        }
        fmt.Println(quant)
    }
}
Gryphon answered 31/5, 2015 at 0:50 Comment(1)
You can only marshal/unmarshal into a public struct member. Those are identified by their initial capital letter. The (un)marshallers (JSON for example) take care of the case conversion for you.Incipit
W
8

A setuid/setgid Go program is reasonably safe, with one major caveat. Go setuid/setgid programs are in general no more, and no less, secure than C/C++ setuid/setgid programs.

It's true that you can force a Go program to dump core by running it with the environment variable GOTRACEBACK=crash and then sending it a signal. However, this is OK for your purposes because the Go program will (try to) create the core dump by sending itself the SIGABRT signal. The kernel will not generate a core dump for a setuid/setgid program killed by a signal.

The major caveat for Go is that on GNU/Linux systems you can not drop back to the original user ID. This is because of how setuid (and setgid, setgroups, setreuid, setregid, setresuid, and setresgid) are implemented for multi-threaded programs on GNU/Linux. The details are at http://golang.org/issue/1435 .

On your final note Uw and Pw need to be capitalized because the standard reflect package does not permit writing to unexported fields.

Wingback answered 31/5, 2015 at 16:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.