aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd/fsu/main.go
blob: af0315eafc3855592c48fe7c683bb1eb3411bbc9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package main

import (
	"bufio"
	"log"
	"os"
	"path"
	"strconv"
	"strings"
	"syscall"
)

const (
	fsuConfFile = "/etc/fsurc"
	envShim     = "FORTIFY_SHIM"
	envAID      = "FORTIFY_APP_ID"

	fpPoison = "INVALIDINVALIDINVALIDINVALIDINVALID"
)

// FortifyPath is the path to fortify, set at compile time.
var FortifyPath = fpPoison

func main() {
	log.SetFlags(0)
	log.SetPrefix("fsu: ")
	log.SetOutput(os.Stderr)

	if os.Geteuid() != 0 {
		log.Fatal("this program must be owned by uid 0 and have the setuid bit set")
	}

	puid := os.Getuid()
	if puid == 0 {
		log.Fatal("this program must not be started by root")
	}

	// validate compiled in fortify path
	if FortifyPath == fpPoison || !path.IsAbs(FortifyPath) {
		log.Fatal("invalid fortify path, this copy of fsu is not compiled correctly")
	}

	pexe := path.Join("/proc", strconv.Itoa(os.Getppid()), "exe")
	if p, err := os.Readlink(pexe); err != nil {
		log.Fatalf("cannot read parent executable path: %v", err)
	} else if strings.HasSuffix(p, " (deleted)") {
		log.Fatal("fortify executable has been deleted")
	} else if p != FortifyPath {
		log.Fatal("this program must be started by fortify")
	}

	// uid = 1000000 +
	//   fid * 10000 +
	//           aid
	uid := 1000000

	// authenticate before accepting user input
	if fid, ok := parseConfig(fsuConfFile, puid); !ok {
		log.Fatalf("uid %d is not in the fsurc file", puid)
	} else {
		uid += fid * 10000
	}

	// pass through setup path to shim
	var shimSetupPath string
	if s, ok := os.LookupEnv(envShim); !ok {
		log.Fatal("FORTIFY_SHIM not set")
	} 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")
	} else {
		uid += aid
	}

	if err := syscall.Setresgid(uid, uid, uid); err != nil {
		log.Fatalf("cannot set gid: %v", err)
	}
	if err := syscall.Setresuid(uid, uid, uid); err != nil {
		log.Fatalf("cannot set uid: %v", err)
	}
	if err := syscall.Exec(FortifyPath, []string{"fortify", "shim"}, []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
	}
}