Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import (
"io"
"math"
"os"
"path/filepath"
"path"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -553,7 +553,11 @@ func (role *DelegatedRole) IsDelegatedPath(targetFilepath string) (bool, error)
for _, pathPattern := range role.Paths {
// A delegated role path may be an explicit path or glob
// pattern (Unix shell-style wildcards).
if isTargetInPathPattern(targetFilepath, pathPattern) {
matched, err := isTargetInPathPattern(targetFilepath, pathPattern)
if err != nil {
return false, err
}
Comment on lines +556 to +559
if matched {
return true, nil
}
}
Expand All @@ -570,24 +574,32 @@ func (role *DelegatedRole) IsDelegatedPath(targetFilepath string) (bool, error)
}

// Determine whether “targetpath“ matches the “pathpattern“.
func isTargetInPathPattern(targetpath string, pathpattern string) bool {
func isTargetInPathPattern(targetpath string, pathpattern string) (bool, error) {
// We need to make sure that targetpath and pathpattern are pointing to
// the same directory as fnmatch doesn't threat "/" as a special symbol.
targetParts := strings.Split(targetpath, "/")
patternParts := strings.Split(pathpattern, "/")
if len(targetParts) != len(patternParts) {
return false
return false, nil
}
Comment on lines 580 to 584

// Every part in the pathpattern could include a glob pattern, that's why
// each of the target and pathpattern parts should match.
// each of the target and pathpattern parts should match. We use path.Match
// (not filepath.Match) so matching is independent of the host OS: TUF target
// paths are always "/"-separated and "\" is a glob escape, not a separator.
// A malformed pattern surfaces path.ErrBadPattern instead of being silently
// treated as a non-match.
for i := 0; i < len(targetParts); i++ {
if ok, _ := filepath.Match(patternParts[i], targetParts[i]); !ok {
return false
ok, err := path.Match(patternParts[i], targetParts[i])
if err != nil {
return false, err
}
if !ok {
return false, nil
}
}

return true
return true, nil
}

// GetRolesForTarget return the names and terminating status of all
Expand Down
13 changes: 13 additions & 0 deletions metadata/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"crypto/sha256"
"encoding/json"
"os"
"path"
"path/filepath"
"testing"
"time"
Expand Down Expand Up @@ -288,6 +289,18 @@ func TestIsDelegatedPath(t *testing.T) {
}
}

func TestIsDelegatedPathInvalidPattern(t *testing.T) {
// A malformed glob (unterminated character class) must surface an error
// rather than being silently treated as a non-match, so callers can tell
// "does not match" apart from "invalid pattern".
role := &DelegatedRole{
Paths: []string{"targets/["},
}
ok, err := role.IsDelegatedPath("targets/anything")
assert.False(t, ok)
assert.ErrorIs(t, err, path.ErrBadPattern)
}

func TestClearSignatures(t *testing.T) {
meta := Root()
// verify signatures is empty
Expand Down
Loading