readExcerpt, ReadNote and ReadScoped now read through brainfs (os.Root, regular files only, type checked on the opened handle), so a symlink swapped in after a check is not followed and a FIFO does not hang the reader. ReadRegularMax bounds the read itself with io.LimitReader(max+1). notes.Target carries the brain root. Refs #157. Adversarial review round 2: PASS (round 1 BLOCKED: unbounded read, rule-9 test names). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #165.
This commit is contained in:
@@ -20,6 +20,9 @@ import (
|
||||
// ErrNotRegular is returned for an entry that is not a regular file.
|
||||
var ErrNotRegular = errors.New("not a regular file")
|
||||
|
||||
// ErrTooLarge is returned by ReadRegularMax for a file over the limit.
|
||||
var ErrTooLarge = errors.New("file too large")
|
||||
|
||||
// Brain is an open brain directory.
|
||||
type Brain struct {
|
||||
root *os.Root
|
||||
@@ -74,7 +77,22 @@ func (b *Brain) ReadRegular(rel string) ([]byte, error) {
|
||||
// cannot create one; fs.protected_hardlinks=1 and the pod's secrets are on a
|
||||
// different filesystem from the brain volume, so a local caller cannot link
|
||||
// them in. Refusing Nlink > 1 is the next step if either changes.
|
||||
func (b *Brain) readOpened(rel string) ([]byte, error) {
|
||||
func (b *Brain) readOpened(rel string) ([]byte, error) { return b.readOpenedMax(rel, -1) }
|
||||
|
||||
// ReadRegularMax is ReadRegular with a size limit, checked on the opened
|
||||
// handle before reading (max < 0 means no limit).
|
||||
func (b *Brain) ReadRegularMax(rel string, max int64) ([]byte, error) {
|
||||
fi, err := b.root.Lstat(rel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !fi.Mode().IsRegular() {
|
||||
return nil, fmt.Errorf("%s: %w", rel, ErrNotRegular)
|
||||
}
|
||||
return b.readOpenedMax(rel, max)
|
||||
}
|
||||
|
||||
func (b *Brain) readOpenedMax(rel string, max int64) ([]byte, error) {
|
||||
f, err := b.root.OpenFile(rel, os.O_RDONLY|syscall.O_NONBLOCK, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -87,9 +105,31 @@ func (b *Brain) readOpened(rel string) ([]byte, error) {
|
||||
if !hfi.Mode().IsRegular() {
|
||||
return nil, fmt.Errorf("%s: %w", rel, ErrNotRegular)
|
||||
}
|
||||
return io.ReadAll(f)
|
||||
if max >= 0 && hfi.Size() > max {
|
||||
return nil, fmt.Errorf("%s is %d bytes (limit %d): %w", rel, hfi.Size(), max, ErrTooLarge)
|
||||
}
|
||||
var r io.Reader = f
|
||||
if wrapAfterStat != nil {
|
||||
r = wrapAfterStat(r)
|
||||
}
|
||||
if max < 0 {
|
||||
return io.ReadAll(r)
|
||||
}
|
||||
// The handle's size can grow after the check; bound the read itself.
|
||||
data, err := io.ReadAll(io.LimitReader(r, max+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if int64(len(data)) > max {
|
||||
return nil, fmt.Errorf("%s grew past %d bytes while read: %w", rel, max, ErrTooLarge)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// wrapAfterStat is a test seam: it runs between the size check and the read,
|
||||
// and wraps the reader the read consumes.
|
||||
var wrapAfterStat func(io.Reader) io.Reader
|
||||
|
||||
// WalkRegular walks a brain-relative directory through the root and calls fn
|
||||
// for each regular file (directories are descended; symlinks, FIFOs and
|
||||
// devices are skipped, never followed). An entry error is passed to onErr and
|
||||
|
||||
Reference in New Issue
Block a user