diff options
author | Florian Klink <flokli@flokli.de> | 2022-12-27T15·11+0100 |
---|---|---|
committer | flokli <flokli@flokli.de> | 2022-12-27T21·31+0000 |
commit | dfd9286f680ef69ff89ab9a9081b2beaabda92be (patch) | |
tree | 4fc5d04e33dff412c4bfddea004a960586b65759 /tvix/store/protos/castore.go | |
parent | c3fb6d22187fc759563b265cfab65a48a6e4fa08 (diff) |
feat(tvix/store/protos): implement Directory.Validate() r/5509
Validate thecks the Directory message for invalid data, such as: - violations of name restrictions - invalid digest lengths - not properly sorted lists - duplicate names in the three lists Change-Id: I8d43a13797793c64097e526ef3bd482c9606c87b Reviewed-on: https://cl.tvl.fyi/c/depot/+/7648 Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
Diffstat (limited to 'tvix/store/protos/castore.go')
-rw-r--r-- | tvix/store/protos/castore.go | 126 |
1 files changed, 126 insertions, 0 deletions
diff --git a/tvix/store/protos/castore.go b/tvix/store/protos/castore.go index 38419b40da99..9969da78a7ca 100644 --- a/tvix/store/protos/castore.go +++ b/tvix/store/protos/castore.go @@ -2,6 +2,7 @@ package storev1 import ( "fmt" + "strings" "google.golang.org/protobuf/proto" "lukechampine.com/blake3" @@ -37,3 +38,128 @@ func (d *Directory) Digest() ([]byte, error) { return h.Sum(nil), nil } + +// isValidName checks a name for validity. +// We disallow slashes, null bytes, '.', '..' and the empty string. +// Depending on the context, a *Node message with an empty string as name is +// allowed, but they don't occur inside a Directory message. +func isValidName(n string) bool { + if n == "" || n == ".." || n == "." || strings.Contains(n, "\x00") || strings.Contains(n, "/") { + return false + } + return true +} + +// Validate thecks the Directory message for invalid data, such as: +// - violations of name restrictions +// - invalid digest lengths +// - not properly sorted lists +// - duplicate names in the three lists +func (d *Directory) Validate() error { + // seenNames contains all seen names so far. + // We populate this to ensure node names are unique across all three lists. + seenNames := make(map[string]interface{}) + + // We also track the last seen name in each of the three lists, + // to ensure nodes are sorted by their names. + lastDirectoryName := "" + lastFileName := "" + lastSymlinkName := "" + + // helper function to only insert in sorted order. + // used with the three lists above. + // Note this consumes a *pointer to* a string, as it mutates it. + insertIfGt := func(lastName *string, name string) error { + // update if it's greater than the previous name + if name > *lastName { + *lastName = name + return nil + } else { + return fmt.Errorf("%v is not in sorted order", name) + } + } + + // insertOnce inserts into seenNames if the key doesn't exist yet. + insertOnce := func(name string) error { + if _, found := seenNames[name]; found { + return fmt.Errorf("duplicate name: %v", name) + } + seenNames[name] = nil + return nil + } + + // Loop over all Directories, Files and Symlinks individually. + // Check the name for validity, check a potential digest for length, + // then check for sorting in the current list, and uniqueness across all three lists. + for _, directoryNode := range d.Directories { + directoryName := directoryNode.GetName() + + // check name for validity + if !isValidName(directoryName) { + return fmt.Errorf("invalid name for DirectoryNode: %v", directoryName) + } + + // check digest to be 32 bytes + digestLen := len(directoryNode.GetDigest()) + if digestLen != 32 { + return fmt.Errorf("invalid digest length for DirectoryNode: %d", digestLen) + } + + // ensure names are sorted + if err := insertIfGt(&lastDirectoryName, directoryName); err != nil { + return err + } + + // add to seenNames + if err := insertOnce(directoryName); err != nil { + return err + } + + } + + for _, fileNode := range d.Files { + fileName := fileNode.GetName() + + // check name for validity + if !isValidName(fileName) { + return fmt.Errorf("invalid name for FileNode: %v", fileName) + } + + // check digest to be 32 bytes + digestLen := len(fileNode.GetDigest()) + if digestLen != 32 { + return fmt.Errorf("invalid digest length for FileNode: %d", digestLen) + } + + // ensure names are sorted + if err := insertIfGt(&lastFileName, fileName); err != nil { + return err + } + + // add to seenNames + if err := insertOnce(fileName); err != nil { + return err + } + } + + for _, symlinkNode := range d.Symlinks { + symlinkName := symlinkNode.GetName() + + // check name for validity + if !isValidName(symlinkName) { + return fmt.Errorf("invalid name for SymlinkNode: %v", symlinkName) + } + + // ensure names are sorted + if err := insertIfGt(&lastSymlinkName, symlinkName); err != nil { + return err + } + + // add to seenNames + if err := insertOnce(symlinkName); err != nil { + return err + } + } + + return nil +} |