aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/fsu
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/fsu')
-rw-r--r--cmd/fsu/main.go114
-rw-r--r--cmd/fsu/parse.go77
2 files changed, 134 insertions, 57 deletions
diff --git a/cmd/fsu/main.go b/cmd/fsu/main.go
index 0ba5a134..1ec3a4bc 100644
--- a/cmd/fsu/main.go
+++ b/cmd/fsu/main.go
@@ -1,10 +1,12 @@
package main
import (
- "bufio"
+ "bytes"
+ "fmt"
"log"
"os"
"path"
+ "slices"
"strconv"
"strings"
"syscall"
@@ -15,11 +17,15 @@ const (
fsuConfFile = "/etc/fsurc"
envShim = "FORTIFY_SHIM"
envAID = "FORTIFY_APP_ID"
+ envGroups = "FORTIFY_GROUPS"
PR_SET_NO_NEW_PRIVS = 0x26
)
-var Fmain = compPoison
+var (
+ Fmain = compPoison
+ Fshim = compPoison
+)
func main() {
log.SetFlags(0)
@@ -35,12 +41,17 @@ func main() {
log.Fatal("this program must not be started by root")
}
- var fmain string
+ var fmain, fshim string
if p, ok := checkPath(Fmain); !ok {
log.Fatal("invalid fortify path, this copy of fsu is not compiled correctly")
} else {
fmain = p
}
+ if p, ok := checkPath(Fshim); !ok {
+ log.Fatal("invalid fshim path, this copy of fsu is not compiled correctly")
+ } else {
+ fshim = p
+ }
pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
if p, err := os.Readlink(pexe); err != nil {
@@ -63,87 +74,76 @@ func main() {
uid += fid * 10000
}
+ // allowed aid range 0 to 9999
+ if as, ok := os.LookupEnv(envAID); !ok {
+ log.Fatal("FORTIFY_APP_ID not set")
+ } else if aid, err := parseUint32Fast(as); err != nil || aid < 0 || aid > 9999 {
+ log.Fatal("invalid aid")
+ } else {
+ uid += aid
+ }
+
// pass through setup path to shim
var shimSetupPath string
if s, ok := os.LookupEnv(envShim); !ok {
- log.Fatal("FORTIFY_SHIM not set")
+ // fortify requests target uid
+ // print resolved uid and exit
+ fmt.Print(uid)
+ os.Exit(0)
} else if !path.IsAbs(s) {
log.Fatal("FORTIFY_SHIM is not absolute")
} else {
shimSetupPath = s
}
- // allowed aid range 0 to 9999
- if as, ok := os.LookupEnv(envAID); !ok {
- log.Fatal("FORTIFY_APP_ID not set")
- } else if aid, err := strconv.Atoi(as); err != nil || aid < 0 || aid > 9999 {
- log.Fatal("invalid aid")
+ // supplementary groups
+ var suppGroups, suppCurrent []int
+
+ if gs, ok := os.LookupEnv(envGroups); ok {
+ if cur, err := os.Getgroups(); err != nil {
+ log.Fatalf("cannot get groups: %v", err)
+ } else {
+ suppCurrent = cur
+ }
+
+ // parse space-separated list of group ids
+ gss := bytes.Split([]byte(gs), []byte{' '})
+ suppGroups = make([]int, len(gss)+1)
+ for i, s := range gss {
+ if gid, err := strconv.Atoi(string(s)); err != nil {
+ log.Fatalf("cannot parse %q: %v", string(s), err)
+ } else if gid > 0 && gid != uid && gid != os.Getgid() && slices.Contains(suppCurrent, gid) {
+ suppGroups[i] = gid
+ } else {
+ log.Fatalf("invalid gid %d", gid)
+ }
+ }
+ suppGroups[len(suppGroups)-1] = uid
} else {
- uid += aid
+ suppGroups = []int{uid}
}
+ // careful! users in the allowlist is effectively allowed to drop groups via fsu
+
if err := syscall.Setresgid(uid, uid, uid); err != nil {
log.Fatalf("cannot set gid: %v", err)
}
+ if err := syscall.Setgroups(suppGroups); err != nil {
+ log.Fatalf("cannot set supplementary groups: %v", err)
+ }
if err := syscall.Setresuid(uid, uid, uid); err != nil {
log.Fatalf("cannot set uid: %v", err)
}
if _, _, errno := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_NO_NEW_PRIVS, 1, 0); errno != 0 {
log.Fatalf("cannot set no_new_privs flag: %s", errno.Error())
}
- if err := syscall.Exec(fmain, []string{"fortify", "shim"}, []string{envShim + "=" + shimSetupPath}); err != nil {
+ if err := syscall.Exec(fshim, []string{"fshim"}, []string{envShim + "=" + shimSetupPath}); err != nil {
log.Fatalf("cannot start shim: %v", err)
}
panic("unreachable")
}
-func parseConfig(p string, puid int) (fid int, ok bool) {
- // refuse to run if fsurc is not protected correctly
- if s, err := os.Stat(p); err != nil {
- log.Fatal(err)
- } else if s.Mode().Perm() != 0400 {
- log.Fatal("bad fsurc perm")
- } else if st := s.Sys().(*syscall.Stat_t); st.Uid != 0 || st.Gid != 0 {
- log.Fatal("fsurc must be owned by uid 0")
- }
-
- if r, err := os.Open(p); err != nil {
- log.Fatal(err)
- return -1, false
- } else {
- s := bufio.NewScanner(r)
- var line int
- for s.Scan() {
- line++
-
- // <puid> <fid>
- lf := strings.SplitN(s.Text(), " ", 2)
- if len(lf) != 2 {
- log.Fatalf("invalid entry on line %d", line)
- }
-
- var puid0 int
- if puid0, err = strconv.Atoi(lf[0]); err != nil || puid0 < 1 {
- log.Fatalf("invalid parent uid on line %d", line)
- }
-
- ok = puid0 == puid
- if ok {
- // allowed fid range 0 to 99
- if fid, err = strconv.Atoi(lf[1]); err != nil || fid < 0 || fid > 99 {
- log.Fatalf("invalid fortify uid on line %d", line)
- }
- return
- }
- }
- if err = s.Err(); err != nil {
- log.Fatalf("cannot read fsurc: %v", err)
- }
- return -1, false
- }
-}
-
func checkPath(p string) (string, bool) {
return p, p != compPoison && p != "" && path.IsAbs(p)
}
diff --git a/cmd/fsu/parse.go b/cmd/fsu/parse.go
new file mode 100644
index 00000000..d0298357
--- /dev/null
+++ b/cmd/fsu/parse.go
@@ -0,0 +1,77 @@
+package main
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+ "syscall"
+)
+
+func parseUint32Fast(s string) (int, error) {
+ sLen := len(s)
+ if sLen < 1 {
+ return -1, errors.New("zero length string")
+ }
+ if sLen > 10 {
+ return -1, errors.New("string too long")
+ }
+
+ n := 0
+ for i, ch := range []byte(s) {
+ ch -= '0'
+ if ch > 9 {
+ return -1, fmt.Errorf("invalid character '%s' at index %d", string([]byte{ch}), i)
+ }
+ n = n*10 + int(ch)
+ }
+ return n, nil
+}
+
+func parseConfig(p string, puid int) (fid int, ok bool) {
+ // refuse to run if fsurc is not protected correctly
+ if s, err := os.Stat(p); err != nil {
+ log.Fatal(err)
+ } else if s.Mode().Perm() != 0400 {
+ log.Fatal("bad fsurc perm")
+ } else if st := s.Sys().(*syscall.Stat_t); st.Uid != 0 || st.Gid != 0 {
+ log.Fatal("fsurc must be owned by uid 0")
+ }
+
+ if r, err := os.Open(p); err != nil {
+ log.Fatal(err)
+ return -1, false
+ } else {
+ s := bufio.NewScanner(r)
+ var line int
+ for s.Scan() {
+ line++
+
+ // <puid> <fid>
+ lf := strings.SplitN(s.Text(), " ", 2)
+ if len(lf) != 2 {
+ log.Fatalf("invalid entry on line %d", line)
+ }
+
+ var puid0 int
+ if puid0, err = parseUint32Fast(lf[0]); err != nil || puid0 < 1 {
+ log.Fatalf("invalid parent uid on line %d", line)
+ }
+
+ ok = puid0 == puid
+ if ok {
+ // allowed fid range 0 to 99
+ if fid, err = parseUint32Fast(lf[1]); err != nil || fid < 0 || fid > 99 {
+ log.Fatalf("invalid fortify uid on line %d", line)
+ }
+ return
+ }
+ }
+ if err = s.Err(); err != nil {
+ log.Fatalf("cannot read fsurc: %v", err)
+ }
+ return -1, false
+ }
+}