mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-08 22:50:33 +00:00
fix: drop newline-bearing member names in arch ParsePackage (#38102)
The arch parser keeps tar member names verbatim. The index writer joins those values one per line into the pacman database. So a member name with a newline adds lines to that package's own `files` entry, which libalpm reads as further fields. The scope is one package record. An uploader cannot forge entries for another package, and can set the same fields in `.PKGINFO` anyway. This is input validation, not a privilege boundary. `ParsePackage` now drops names that contain CR or LF. `joinFields` drops such values again when writing the index, which also covers packages that are already stored. Real packages never carry newlines in file paths, so well-formed uploads are unaffected. --------- Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -153,6 +153,9 @@ func ParsePackage(r io.Reader) (*Package, error) {
|
||||
return nil, err
|
||||
}
|
||||
} else if !strings.HasPrefix(filename, ".") {
|
||||
if strings.ContainsAny(hd.Name, "\n\r") {
|
||||
continue // a newline would forge extra lines in the pacman index
|
||||
}
|
||||
if err := files.Add(hd.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -104,6 +104,7 @@ func TestParsePackage(t *testing.T) {
|
||||
data := createPackage(c, map[string][]byte{
|
||||
".PKGINFO": createPKGINFOContent(packageName, packageVersion),
|
||||
"/test/dummy.txt": {},
|
||||
"usr/lib/legit\n\n%FILES%\n/etc/cron.d/x": {}, // must not reach the file list
|
||||
})
|
||||
|
||||
p, err := ParsePackage(data)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -360,9 +361,16 @@ type keyValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
// pacman parses the index line by line, so a value with a newline could forge extra fields
|
||||
func joinFields(values []string) string {
|
||||
return strings.Join(slices.DeleteFunc(slices.Clone(values), func(value string) bool {
|
||||
return strings.ContainsAny(value, "\n\r")
|
||||
}), "\n")
|
||||
}
|
||||
|
||||
func writeFiles(tw *tar.Writer, opts *entryOptions) error {
|
||||
return writeFields(tw, fmt.Sprintf("%s-%s/files", opts.Package.Name, opts.Version.Version), []keyValue{
|
||||
{"FILES", strings.Join(opts.FileMetadata.Files, "\n")},
|
||||
{"FILES", joinFields(opts.FileMetadata.Files)},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -381,17 +389,17 @@ func writeDescription(tw *tar.Writer, opts *entryOptions) error {
|
||||
{"VERSION", opts.Version.Version},
|
||||
{"DESC", opts.VersionMetadata.Description},
|
||||
{"URL", opts.VersionMetadata.ProjectURL},
|
||||
{"LICENSE", strings.Join(opts.VersionMetadata.Licenses, "\n")},
|
||||
{"GROUPS", strings.Join(opts.FileMetadata.Groups, "\n")},
|
||||
{"LICENSE", joinFields(opts.VersionMetadata.Licenses)},
|
||||
{"GROUPS", joinFields(opts.FileMetadata.Groups)},
|
||||
{"BUILDDATE", strconv.FormatInt(opts.FileMetadata.BuildDate, 10)},
|
||||
{"PACKAGER", opts.FileMetadata.Packager},
|
||||
{"PROVIDES", strings.Join(opts.FileMetadata.Provides, "\n")},
|
||||
{"REPLACES", strings.Join(opts.FileMetadata.Replaces, "\n")},
|
||||
{"CONFLICTS", strings.Join(opts.FileMetadata.Conflicts, "\n")},
|
||||
{"DEPENDS", strings.Join(opts.FileMetadata.Depends, "\n")},
|
||||
{"OPTDEPENDS", strings.Join(opts.FileMetadata.OptDepends, "\n")},
|
||||
{"MAKEDEPENDS", strings.Join(opts.FileMetadata.MakeDepends, "\n")},
|
||||
{"CHECKDEPENDS", strings.Join(opts.FileMetadata.CheckDepends, "\n")},
|
||||
{"PROVIDES", joinFields(opts.FileMetadata.Provides)},
|
||||
{"REPLACES", joinFields(opts.FileMetadata.Replaces)},
|
||||
{"CONFLICTS", joinFields(opts.FileMetadata.Conflicts)},
|
||||
{"DEPENDS", joinFields(opts.FileMetadata.Depends)},
|
||||
{"OPTDEPENDS", joinFields(opts.FileMetadata.OptDepends)},
|
||||
{"MAKEDEPENDS", joinFields(opts.FileMetadata.MakeDepends)},
|
||||
{"CHECKDEPENDS", joinFields(opts.FileMetadata.CheckDepends)},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package arch
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestJoinFields(t *testing.T) {
|
||||
values := []string{"usr/bin/a", "usr/bin/b\n\n%FILES%\netc/cron.d/x", "usr/bin/c"}
|
||||
|
||||
assert.Equal(t, "usr/bin/a\nusr/bin/c", joinFields(values))
|
||||
assert.Len(t, values, 3) // must not modify the caller's slice
|
||||
}
|
||||
Reference in New Issue
Block a user