fix(packages): validate debian distribution and component names (#38116)

**Newline injection into the Debian Release and Packages indices**

The `distribution` and `component` come straight from the request path
and are written line by line into the generated `Release` and `Packages`
files (the `Suite`/`Codename`/`Components` lines and the `Filename:
pool/<distribution>/<component>/...` line), but `UploadPackageFile` only
checked they were non-empty. `ctx.PathParam` url-decodes the segment, so
an encoded newline such as `main%0AInjected-Field: x` is accepted,
stored and then re-emitted for that distribution, which lets an
authenticated uploader forge extra fields in the index apt consumes.
Restricted both values to a conservative name pattern in the handler,
since that is the layer that accepts them; this should also keep the
pool paths well formed.

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
metsw24-max
2026-06-29 06:50:22 +00:00
committed by GitHub
co-authored by wxiaoguang
parent 762c674bc5
commit 0c67849e68
3 changed files with 79 additions and 24 deletions
+37 -5
View File
@@ -49,7 +49,7 @@ func TestParsePackage(t *testing.T) {
p, err := ParsePackage(data)
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrMissingControlFile)
assert.ErrorIs(t, err, GlobalVars().ErrMissingControlFile)
})
t.Run("Compression", func(t *testing.T) {
@@ -58,7 +58,7 @@ func TestParsePackage(t *testing.T) {
p, err := ParsePackage(data)
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrUnsupportedCompression)
assert.ErrorIs(t, err, GlobalVars().ErrUnsupportedCompression)
})
var buf bytes.Buffer
@@ -141,7 +141,7 @@ func TestParseControlFile(t *testing.T) {
for _, name := range []string{"", "-cd"} {
p, err := ParseControlFile(buildContent(name, packageVersion, packageArchitecture))
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrInvalidName)
assert.ErrorIs(t, err, GlobalVars().ErrInvalidName)
}
})
@@ -149,14 +149,14 @@ func TestParseControlFile(t *testing.T) {
for _, version := range []string{"", "1-", ":1.0", "1_0"} {
p, err := ParseControlFile(buildContent(packageName, version, packageArchitecture))
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrInvalidVersion)
assert.ErrorIs(t, err, GlobalVars().ErrInvalidVersion)
}
})
t.Run("InvalidArchitecture", func(t *testing.T) {
p, err := ParseControlFile(buildContent(packageName, packageVersion, ""))
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrInvalidArchitecture)
assert.ErrorIs(t, err, GlobalVars().ErrInvalidArchitecture)
})
t.Run("Valid", func(t *testing.T) {
@@ -200,3 +200,35 @@ func TestParseControlFile(t *testing.T) {
assert.NotContains(t, p.Control, "evil.deb")
})
}
func TestValidateDistributionOrComponent(t *testing.T) {
bad := []string{
"",
".",
"..",
"-stable",
".hidden",
"a/b",
"a b",
"bookworm\nSigned-By: evil",
"main\nFilename: pool/x",
"a\tb",
}
for _, name := range bad {
assert.False(t, IsValidDistributionOrComponent(name), "bad=%q", name)
}
good := []string{
"stable",
"bookworm",
"bookworm-backports",
"stable-updates",
"main",
"non-free-firmware",
"a",
"1",
}
for _, name := range good {
assert.True(t, IsValidDistributionOrComponent(name), "good=%q", name)
}
}