internal/pkg: optionally validate flat pathnames
All checks were successful
Test / Create distribution (push) Successful in 43s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m35s
Test / ShareFS (push) Successful in 3m37s
Test / Hpkg (push) Successful in 4m21s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 5m50s
Test / Flake checks (push) Successful in 1m42s
All checks were successful
Test / Create distribution (push) Successful in 43s
Test / Sandbox (push) Successful in 2m34s
Test / Hakurei (push) Successful in 3m35s
Test / ShareFS (push) Successful in 3m37s
Test / Hpkg (push) Successful in 4m21s
Test / Sandbox (race detector) (push) Successful in 4m57s
Test / Hakurei (race detector) (push) Successful in 5m50s
Test / Flake checks (push) Successful in 1m42s
This makes the decoder safe against untrusted input without hurting performance for a trusted stream. This should still not be called against untrusted input though. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"io/fs"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"hakurei.app/container/check"
|
||||
@@ -52,8 +53,12 @@ func (ent *FlatEntry) Encode(w io.Writer) (n int, err error) {
|
||||
return w.Write(payload)
|
||||
}
|
||||
|
||||
// ErrInsecurePath is returned by [FlatEntry.Decode] if validation is requested
|
||||
// and a nonlocal path is encountered in the stream.
|
||||
var ErrInsecurePath = errors.New("insecure file path")
|
||||
|
||||
// Decode decodes the entry from its representation produced by Encode.
|
||||
func (ent *FlatEntry) Decode(r io.Reader) (n int, err error) {
|
||||
func (ent *FlatEntry) Decode(r io.Reader, validate bool) (n int, err error) {
|
||||
var nr int
|
||||
|
||||
header := make([]byte, wordSize*2)
|
||||
@@ -92,6 +97,11 @@ func (ent *FlatEntry) Decode(r io.Reader) (n int, err error) {
|
||||
} else {
|
||||
ent.Data = buf[pPathSize : pPathSize+dataSize]
|
||||
}
|
||||
|
||||
if validate && !filepath.IsLocal(ent.Path) {
|
||||
err = ErrInsecurePath
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -108,11 +118,16 @@ type DirScanner struct {
|
||||
// Entry to store results in. Its address is returned by the Entry method
|
||||
// and is updated on every call to Scan.
|
||||
ent FlatEntry
|
||||
|
||||
// Validate pathnames during decoding.
|
||||
validate bool
|
||||
}
|
||||
|
||||
// NewDirScanner returns the address of a new instance of [DirScanner] reading
|
||||
// from r. The caller must no longer read from r after this function returns.
|
||||
func NewDirScanner(r io.Reader) *DirScanner { return &DirScanner{r: r} }
|
||||
func NewDirScanner(r io.Reader, validate bool) *DirScanner {
|
||||
return &DirScanner{r: r, validate: validate}
|
||||
}
|
||||
|
||||
// Err returns the first non-EOF I/O error.
|
||||
func (s *DirScanner) Err() error {
|
||||
@@ -132,7 +147,7 @@ func (s *DirScanner) Scan() bool {
|
||||
}
|
||||
|
||||
var n int
|
||||
n, s.err = s.ent.Decode(s.r)
|
||||
n, s.err = s.ent.Decode(s.r, s.validate)
|
||||
if errors.Is(s.err, io.EOF) {
|
||||
return n != 0
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestFlatten(t *testing.T) {
|
||||
t.Fatalf("Flatten: error = %v", err)
|
||||
}
|
||||
|
||||
s := pkg.NewDirScanner(bytes.NewReader(buf.Bytes()))
|
||||
s := pkg.NewDirScanner(bytes.NewReader(buf.Bytes()), true)
|
||||
var got []pkg.FlatEntry
|
||||
for s.Scan() {
|
||||
got = append(got, *s.Entry())
|
||||
|
||||
Reference in New Issue
Block a user