Skip to content

Commit

Permalink
helper/hasher general improvements (#41863)
Browse files Browse the repository at this point in the history
 * Fix a TOCTOU by opening the file handle and then doing stat(), instead of
   doning stat() and then opening.
 * Make sure this is a regular file, otherwise you could trick auditbeat into
   hashing an infinite source like a pipe.
 * Allow for rate (but not file size) to be infinite, this is needed for an
   upcoming new backend for module/system/process.
 * Finally, fix error messages that show up on ECS, see below.

before:
```
failed to hash executable /d/e/beats/x-pack/auditbeat/auditbeat for PID 50751: failed to hash file /d/e/beats/x-pack/auditbeat/auditbeat: hasher: file size 143673152 exceeds max file size
```

after:
```
failed to hash executable /d/e/beats/x-pack/auditbeat/auditbeat for PID 50804: size 143673152 exceeds max file size
```

(cherry picked from commit 8b38b65)
  • Loading branch information
haesbaert authored and mergify[bot] committed Dec 3, 2024
1 parent fb6f3f9 commit 38794f4
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 14 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.next.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ https://github.com/elastic/beats/compare/v8.8.1\...main[Check the HEAD diff]

*Auditbeat*

- auditd: Request status from a separate socket to avoid data congestion {pull}41207[41207]
- auditd: Use ECS `event.type: end` instead of `stop` for SERVICE_STOP, DAEMON_ABORT, and DAEMON_END messages. {pull}41558[41558]
- auditd: Update syscall names for Linux 6.11. {pull}41558[41558]
- hasher: Geneneral improvements and fixes. {pull}41863[41863]

*Filebeat*

Expand Down
43 changes: 29 additions & 14 deletions auditbeat/helper/hasher/hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
"fmt"
"hash"
"io"
"os"
"strings"
"time"

Expand Down Expand Up @@ -124,7 +123,7 @@ type FileTooLargeError struct {

// Error returns the error message for FileTooLargeError.
func (e FileTooLargeError) Error() string {
return fmt.Sprintf("hasher: file size %d exceeds max file size", e.fileSize)
return fmt.Sprintf("size %d exceeds max file size", e.fileSize)
}

// Config contains the configuration of a FileHasher.
Expand Down Expand Up @@ -174,28 +173,46 @@ type FileHasher struct {

// NewFileHasher creates a new FileHasher.
func NewFileHasher(c Config, done <-chan struct{}) (*FileHasher, error) {
var limit rate.Limit

if c.ScanRateBytesPerSec == 0 {
limit = rate.Inf
} else {
limit = rate.Limit(c.ScanRateBytesPerSec)
}

return &FileHasher{
config: c,
limiter: rate.NewLimiter(
rate.Limit(c.ScanRateBytesPerSec), // Rate
int(c.MaxFileSizeBytes), // Burst
limit, // Rate
int(c.MaxFileSizeBytes), // Burst
),
done: done,
}, nil
}

// HashFile hashes the contents of a file.
func (hasher *FileHasher) HashFile(path string) (map[HashType]Digest, error) {
info, err := os.Stat(path)
f, err := file.ReadOpen(path)
if err != nil {
return nil, fmt.Errorf("failed to stat file %v: %w", path, err)
return nil, fmt.Errorf("open: %w", err)
}
defer f.Close()

info, err := f.Stat()
if err != nil {
return nil, fmt.Errorf("stat: %w", err)
}
if !info.Mode().IsRegular() {
return nil, fmt.Errorf("not a regular file")

}

// Throttle reading and hashing rate.
if len(hasher.config.HashTypes) > 0 {
err = hasher.throttle(info.Size())
if err != nil {
return nil, fmt.Errorf("failed to hash file %v: %w", path, err)
return nil, err
}
}

Expand All @@ -210,15 +227,9 @@ func (hasher *FileHasher) HashFile(path string) (map[HashType]Digest, error) {
}

if len(hashes) > 0 {
f, err := file.ReadOpen(path)
if err != nil {
return nil, fmt.Errorf("failed to open file for hashing: %w", err)
}
defer f.Close()

hashWriter := multiWriter(hashes)
if _, err := io.Copy(hashWriter, f); err != nil {
return nil, fmt.Errorf("failed to calculate file hashes: %w", err)
return nil, err
}

nameToHash := make(map[HashType]Digest, len(hashes))
Expand All @@ -233,6 +244,10 @@ func (hasher *FileHasher) HashFile(path string) (map[HashType]Digest, error) {
}

func (hasher *FileHasher) throttle(fileSize int64) error {
// Burst is ignored if limit is infinite, so check it manually
if hasher.limiter.Limit() == rate.Inf && int(fileSize) > hasher.limiter.Burst() {
return FileTooLargeError{fileSize}
}
reservation := hasher.limiter.ReserveN(time.Now(), int(fileSize))
if !reservation.OK() {
// File is bigger than the max file size
Expand Down

0 comments on commit 38794f4

Please sign in to comment.