From 5451247fa2157627293ba110a0fce13dd39b3b3c Mon Sep 17 00:00:00 2001 From: Mateus Melchiades Date: Tue, 6 Feb 2024 19:15:28 +0000 Subject: [PATCH 1/2] feat: Improve validation performance This commit improves validation performance by changing the parallelism model to a bag-of-tasks where the number of workers is equal to the number of system threads. This reduces the overhead of switching between potentially thousands of threads. --- core/suidcheck.go | 1 - core/validate.go | 128 +++++++++++++++++++++++++++------------------- 2 files changed, 74 insertions(+), 55 deletions(-) diff --git a/core/suidcheck.go b/core/suidcheck.go index 5ae71e8..5f538cf 100644 --- a/core/suidcheck.go +++ b/core/suidcheck.go @@ -17,7 +17,6 @@ func ValidateSUID(file string, isSUID bool) { } else if fileStat.Mode()&os.ModeSetuid == 0 && isSUID { fmt.Printf("[FAIL] File %s has incorrect suid permission\n", file) correctSUID(file, isSUID) - } else { } } diff --git a/core/validate.go b/core/validate.go index 35b772d..2ebf53f 100644 --- a/core/validate.go +++ b/core/validate.go @@ -1,17 +1,73 @@ package core import ( + "bufio" + "bytes" "crypto/sha1" "fmt" "io" "os" - "strconv" + "runtime" "strings" "sync" - - "github.com/linux-immutability-tools/FsGuard/config" ) +func validatePathThread(dataCh chan string, errCh chan error, wg *sync.WaitGroup) { + defer wg.Done() + + var file *os.File = nil + var err error = nil + for data := range dataCh { + if len(data) == 0 { + return + } + + idx := strings.Index(data, " #FSG# ") + if idx == -1 { + errCh <- fmt.Errorf("[FAIL] %s - Malformed line", data) + continue + } + + name := data[:idx] + sig := data[idx+7 : idx+47] + var isSUID bool + switch data[idx+54:] { + case "true": + isSUID = true + case "false": + isSUID = false + default: + errCh <- fmt.Errorf("[FAIL] %s - Cannot find suid value", data[idx+54:]) + continue + } + + if file, err = os.Open(name); err != nil { + if os.IsNotExist(err) { + errCh <- fmt.Errorf("[FAIL] %s - File not found", name) + continue + } + errCh <- err + continue + } + + sha1sum, err := calculateHash(file) + if err != nil { + file.Close() + errCh <- err + continue + } + file.Close() + + if err = validateChecksum(name, sha1sum, sig); err != nil { + errCh <- err + continue + } + + ValidateSUID(name, isSUID) + fmt.Printf("[OK] %s - %s\n", name, sha1sum) + } +} + func ValidatePath(recipePath string) error { data, err := os.ReadFile(recipePath) if err != nil { @@ -19,70 +75,34 @@ func ValidatePath(recipePath string) error { } var wg sync.WaitGroup - errCh := make(chan error, 1) - - for _, file := range strings.Split(string(data), "\n") { - if strings.TrimSpace(file) == "" { - continue - } - properties := strings.Split(file, " #FSG# ") + threads := runtime.NumCPU() + errCh := make(chan error) + dataCh := make(chan string, threads) + for i := 0; i < threads; i++ { wg.Add(1) - go func(prop []string) { - defer wg.Done() - if _, err := os.Stat(prop[0]); os.IsNotExist(err) { - errCh <- fmt.Errorf("[FAIL] %s - File not found", prop[0]) - return - } - - file, err := os.Open(prop[0]) - if err != nil { - errCh <- err - return - } - defer file.Close() - - sha1sum, err := calculateHash(file) - if err != nil { - errCh <- err - return - } + go validatePathThread(dataCh, errCh, &wg) + } - prop[1] = strings.TrimSpace(prop[1]) - - failed := false - errOut := "" - if err := validateChecksum(prop[0], sha1sum, prop[1]); err != nil { - if config.QuitOnFail { - errCh <- err - return - } else { - failed = true - errOut = err.Error() - } - } + scanner := bufio.NewScanner(bytes.NewReader(data)) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + dataCh <- scanner.Text() + } - isSUID, err := strconv.ParseBool(prop[2]) - if err != nil { - errCh <- fmt.Errorf("[FAIL] %s - Cannot find suid value", prop[0]) - return - } - ValidateSUID(prop[0], isSUID) - if config.QuitOnFail || !failed { - fmt.Printf("[OK] %s - %s\n", prop[0], sha1sum) - } else { - fmt.Printf("%s\n", errOut) - } - }(properties) + for i := 0; i < threads; i++ { + dataCh <- "" } go func() { wg.Wait() close(errCh) + close(dataCh) }() for err := range errCh { if err != nil { + fmt.Println(err) return err } } From 68b8181c13e8488f0ca7bc5a8aa82c1cb1bdcc88 Mon Sep 17 00:00:00 2001 From: Mateus Melchiades Date: Tue, 6 Feb 2024 16:54:43 -0300 Subject: [PATCH 2/2] Re-introduce quit on fail --- core/validate.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/core/validate.go b/core/validate.go index 2ebf53f..75d2084 100644 --- a/core/validate.go +++ b/core/validate.go @@ -10,6 +10,8 @@ import ( "runtime" "strings" "sync" + + "github.com/linux-immutability-tools/FsGuard/config" ) func validatePathThread(dataCh chan string, errCh chan error, wg *sync.WaitGroup) { @@ -58,13 +60,22 @@ func validatePathThread(dataCh chan string, errCh chan error, wg *sync.WaitGroup } file.Close() + failed := false if err = validateChecksum(name, sha1sum, sig); err != nil { - errCh <- err - continue + if config.QuitOnFail { + errCh <- err + continue + } else { + failed = true + } } ValidateSUID(name, isSUID) - fmt.Printf("[OK] %s - %s\n", name, sha1sum) + if !failed { + fmt.Printf("[OK] %s - %s\n", name, sha1sum) + } else { + fmt.Printf("%s\n", err) + } } }