`files.Put()` and `files.PutTombstone()` accept arbitrary `sig.Name` values without validation. This allows `../` paths to resolve outside the state directory. The NixOS module/systemd service would have prevented this from being a real problem, but best to lock it down all the same.
75 lines
1.8 KiB
Go
75 lines
1.8 KiB
Go
package model
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"git.clan.lol/clan/data-mesher/pkg/crypto"
|
|
)
|
|
|
|
var (
|
|
NameRegex = regexp.MustCompile(`^[a-z0-9_]{1,255}(/[a-z0-9_]{1,255})*$`)
|
|
NamespaceNameRegex = regexp.MustCompile(`^[a-z0-9_]{1,255}$`)
|
|
|
|
ErrInvalidName = errors.New("invalid name")
|
|
)
|
|
|
|
// ValidateName checks that a file name is either a valid static file name (matching NameRegex) or a valid namespace
|
|
// path ({namespace}/{url_encoded_key}).
|
|
// This rejects path traversal sequences and any name that does not conform to the expected formats.
|
|
func ValidateName(name string) error {
|
|
if name == "" {
|
|
return fmt.Errorf("%w: empty name", ErrInvalidName)
|
|
}
|
|
|
|
if strings.ContainsRune(name, 0) {
|
|
return fmt.Errorf("%w: contains null byte", ErrInvalidName)
|
|
}
|
|
|
|
if !fs.ValidPath(name) {
|
|
return fmt.Errorf("%w: %s", ErrInvalidName, name)
|
|
}
|
|
|
|
if !filepath.IsLocal(name) {
|
|
return fmt.Errorf("%w: %s", ErrInvalidName, name)
|
|
}
|
|
|
|
if NameRegex.MatchString(name) {
|
|
return nil
|
|
}
|
|
|
|
if _, _, err := ParseNamespacePath(name); err == nil {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("%w: %s", ErrInvalidName, name)
|
|
}
|
|
|
|
func ValidateNamespaceName(name string) error {
|
|
if !NamespaceNameRegex.MatchString(name) {
|
|
return fmt.Errorf("invalid namespace name: %s", name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ParseNamespacePath checks if a file name matches the pattern {namespace}/{url_encoded_key}
|
|
// and returns the namespace and parsed public key.
|
|
func ParseNamespacePath(name string) (string, *crypto.PublicKey, error) {
|
|
parts := strings.SplitN(name, "/", 2)
|
|
if len(parts) != 2 {
|
|
return "", nil, fmt.Errorf("not a namespace path: %s", name)
|
|
}
|
|
|
|
signerKey, err := crypto.ParsePublicKeyURLEncoded(parts[1])
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("invalid signer key in namespace path: %w", err)
|
|
}
|
|
|
|
return parts[0], signerKey, nil
|
|
}
|