aboutsummaryrefslogtreecommitdiffhomepage
path: root/cmd
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-02-14 16:43:55 +0900
committerOphestra <cat@gensokyo.uk>2025-02-14 16:43:55 +0900
commit1fd571d5619459bd29077b84418d913a8c5462a0 (patch)
tree70e0ffe8e31b25416356565df97c9cd5ceb544fc /cmd
parentbe30e2f11ebc94fb8a78abe61a5d5531c38805b7 (diff)
cmd/fsu: check parse behaviour
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'cmd')
-rw-r--r--cmd/fsu/main.go13
-rw-r--r--cmd/fsu/parse.go70
-rw-r--r--cmd/fsu/parse_test.go96
3 files changed, 138 insertions, 41 deletions
diff --git a/cmd/fsu/main.go b/cmd/fsu/main.go
index 1f90a390..fd3ba1e2 100644
--- a/cmd/fsu/main.go
+++ b/cmd/fsu/main.go
@@ -61,8 +61,19 @@ func main() {
// aid
uid := 1000000
+ // refuse to run if fsurc is not protected correctly
+ if s, err := os.Stat(fsuConfFile); 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")
+ }
+
// authenticate before accepting user input
- if fid, ok := parseConfig(fsuConfFile, puid); !ok {
+ if f, err := os.Open(fsuConfFile); err != nil {
+ log.Fatal(err)
+ } else if fid, ok := mustParseConfig(f, puid); !ok {
log.Fatalf("uid %d is not in the fsurc file", puid)
} else {
uid += fid * 10000
diff --git a/cmd/fsu/parse.go b/cmd/fsu/parse.go
index 5ed53f5f..4b3e22a8 100644
--- a/cmd/fsu/parse.go
+++ b/cmd/fsu/parse.go
@@ -4,10 +4,9 @@ import (
"bufio"
"errors"
"fmt"
+ "io"
"log"
- "os"
"strings"
- "syscall"
)
func parseUint32Fast(s string) (int, error) {
@@ -30,48 +29,39 @@ func parseUint32Fast(s string) (int, error) {
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++
+func parseConfig(r io.Reader, puid int) (fid int, ok bool, err error) {
+ s := bufio.NewScanner(r)
+ var line, puid0 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)
- }
+ // <puid> <fid>
+ lf := strings.SplitN(s.Text(), " ", 2)
+ if len(lf) != 2 {
+ return -1, false, fmt.Errorf("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)
- }
+ puid0, err = parseUint32Fast(lf[0])
+ if err != nil || puid0 < 1 {
+ return -1, false, fmt.Errorf("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
+ ok = puid0 == puid
+ if ok {
+ // allowed fid range 0 to 99
+ if fid, err = parseUint32Fast(lf[1]); err != nil || fid < 0 || fid > 99 {
+ return -1, false, fmt.Errorf("invalid fortify uid on line %d", line)
}
+ return
}
- if err = s.Err(); err != nil {
- log.Fatalf("cannot read fsurc: %v", err)
- }
- return -1, false
}
+ return -1, false, s.Err()
+}
+
+func mustParseConfig(r io.Reader, puid int) (int, bool) {
+ fid, ok, err := parseConfig(r, puid)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return fid, ok
}
diff --git a/cmd/fsu/parse_test.go b/cmd/fsu/parse_test.go
new file mode 100644
index 00000000..baa239bc
--- /dev/null
+++ b/cmd/fsu/parse_test.go
@@ -0,0 +1,96 @@
+package main
+
+import (
+ "bytes"
+ "strconv"
+ "testing"
+)
+
+func Test_parseUint32Fast(t *testing.T) {
+ t.Run("zero-length", func(t *testing.T) {
+ if _, err := parseUint32Fast(""); err == nil || err.Error() != "zero length string" {
+ t.Errorf(`parseUint32Fast(""): error = %v`, err)
+ return
+ }
+ })
+ t.Run("overflow", func(t *testing.T) {
+ if _, err := parseUint32Fast("10000000000"); err == nil || err.Error() != "string too long" {
+ t.Errorf("parseUint32Fast: error = %v", err)
+ return
+ }
+ })
+ t.Run("invalid byte", func(t *testing.T) {
+ if _, err := parseUint32Fast("meow"); err == nil || err.Error() != "invalid character 'm' at index 0" {
+ t.Errorf(`parseUint32Fast("meow"): error = %v`, err)
+ return
+ }
+ })
+ t.Run("full range", func(t *testing.T) {
+ testRange := func(i, end int) {
+ for ; i < end; i++ {
+ s := strconv.Itoa(i)
+ w := i
+ t.Run("parse "+s, func(t *testing.T) {
+ t.Parallel()
+ v, err := parseUint32Fast(s)
+ if err != nil {
+ t.Errorf("parseUint32Fast(%q): error = %v",
+ s, err)
+ return
+ }
+ if v != w {
+ t.Errorf("parseUint32Fast(%q): got %v",
+ s, v)
+ return
+ }
+ })
+ }
+ }
+
+ testRange(0, 5000)
+ testRange(105000, 110000)
+ testRange(23005000, 23010000)
+ testRange(456005000, 456010000)
+ testRange(7890005000, 7890010000)
+ })
+}
+
+func Test_parseConfig(t *testing.T) {
+ testCases := []struct {
+ name string
+ puid, want int
+ wantErr string
+ rc string
+ }{
+ {"empty", 0, -1, "", ``},
+ {"invalid field", 0, -1, "invalid entry on line 1", `9`},
+ {"invalid puid", 0, -1, "invalid parent uid on line 1", `f 9`},
+ {"invalid fid", 1000, -1, "invalid fortify uid on line 1", `1000 f`},
+ {"match", 1000, 0, "", `1000 0`},
+ }
+
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ fid, ok, err := parseConfig(bytes.NewBufferString(tc.rc), tc.puid)
+ if err == nil && tc.wantErr != "" {
+ t.Errorf("parseConfig: error = %v; wantErr %q",
+ err, tc.wantErr)
+ return
+ }
+ if err != nil && err.Error() != tc.wantErr {
+ t.Errorf("parseConfig: error = %q; wantErr %q",
+ err, tc.wantErr)
+ return
+ }
+ if ok == (tc.want == -1) {
+ t.Errorf("parseConfig: ok = %v; want %v",
+ ok, tc.want)
+ return
+ }
+ if fid != tc.want {
+ t.Errorf("parseConfig: fid = %v; want %v",
+ fid, tc.want)
+ }
+ })
+ }
+}