Harden Windows package paths (T-605)
This commit is contained in:
+7
-15
@@ -9,12 +9,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"softbox.local/core/domain"
|
||||
"softbox.local/core/internal/safepath"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -163,8 +163,12 @@ func validateApp(app App) error {
|
||||
if len(app.Architectures) == 0 {
|
||||
return invalidField("architectures", "must not be empty")
|
||||
}
|
||||
if !validSafeRelativePath(app.EntryEXE) {
|
||||
return invalidField("entry_exe", "must be a safe relative path")
|
||||
if err := safepath.ValidateRelative(app.EntryEXE); err != nil {
|
||||
return invalidField(
|
||||
"entry_exe",
|
||||
"must be a safe Windows relative path: %v",
|
||||
err,
|
||||
)
|
||||
}
|
||||
if len(app.Packages) == 0 {
|
||||
return invalidField("packages", "must not be empty")
|
||||
@@ -261,18 +265,6 @@ func validateHTTPSURL(value string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func validSafeRelativePath(value string) bool {
|
||||
if value == "" || strings.Contains(value, `\`) || strings.Contains(value, ":") {
|
||||
return false
|
||||
}
|
||||
cleaned := path.Clean(value)
|
||||
return cleaned == value &&
|
||||
cleaned != "." &&
|
||||
!strings.HasPrefix(cleaned, "/") &&
|
||||
cleaned != ".." &&
|
||||
!strings.HasPrefix(cleaned, "../")
|
||||
}
|
||||
|
||||
func invalidField(field, format string, values ...any) error {
|
||||
return fmt.Errorf(
|
||||
"%w: %s: %s",
|
||||
|
||||
@@ -70,6 +70,45 @@ func TestParserRejectsDuplicateAppIDAndPackageMismatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserRejectsUnsafeWindowsEntrypoints(t *testing.T) {
|
||||
for _, entrypoint := range []string{
|
||||
".. /escape.exe",
|
||||
"bin./App.exe",
|
||||
"App.exe.",
|
||||
"App.exe ",
|
||||
" App.exe",
|
||||
"NUL",
|
||||
"con.txt",
|
||||
"bad?.exe",
|
||||
`bin\App.exe`,
|
||||
} {
|
||||
t.Run(entrypoint, func(t *testing.T) {
|
||||
manifest := validManifestForTest()
|
||||
manifest.Apps[0].EntryEXE = entrypoint
|
||||
_, err := parseSignedManifestForTest(t, manifest, ChannelModern)
|
||||
if !errors.Is(err, ErrInvalidManifest) {
|
||||
t.Fatalf("Parse() error = %v, want %v", err, ErrInvalidManifest)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParserAcceptsUnicodeNestedEntrypoint(t *testing.T) {
|
||||
manifest := validManifestForTest()
|
||||
manifest.Apps[0].EntryEXE = "工具/解析器.exe"
|
||||
parsed, err := parseSignedManifestForTest(t, manifest, ChannelModern)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse() error = %v", err)
|
||||
}
|
||||
if parsed.Apps[0].EntryEXE != manifest.Apps[0].EntryEXE {
|
||||
t.Fatalf(
|
||||
"EntryEXE = %q, want %q",
|
||||
parsed.Apps[0].EntryEXE,
|
||||
manifest.Apps[0].EntryEXE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func signedFixture(t *testing.T, name string) (Verifier, []byte) {
|
||||
t.Helper()
|
||||
publicKey, privateKey := catalogTestKey()
|
||||
|
||||
Reference in New Issue
Block a user