From 4b743d2e8c15c472a93bf7481c5fec1e0d93b000 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Tue, 20 Oct 2026 22:24:52 -0700 Subject: [PATCH] fix(packages): reject newline-bearing rubygems dependency metadata (#39782) (#39900) Backport #39782 by @basavaraj-sm05 The RubyGems compact index (the shared `/versions` file and per-package `info/` files) is line-based. https://github.com/go-gitea/gitea/pull/38061 rejected line breaks in the gem name, but the platform, dependency names and version requirements from the gemspec still accepted them, letting an uploader forge index lines for bundler clients. The parser now rejects a line break in any of those fields. The Marshal based endpoints are length prefixed and unaffected. Co-authored-by: Basavaraj S m Co-authored-by: silverwind --- modules/packages/rubygems/metadata.go | 17 +++++++++++++++++ modules/packages/rubygems/metadata_test.go | 13 +++++++++++++ 2 files changed, 30 insertions(+) diff --git a/modules/packages/rubygems/metadata.go b/modules/packages/rubygems/metadata.go index 2258034ba45..c98334bff6b 100644 --- a/modules/packages/rubygems/metadata.go +++ b/modules/packages/rubygems/metadata.go @@ -8,6 +8,8 @@ import ( "compress/gzip" "io" "regexp" + "slices" + "strings" "sync" "gitea.dev/modules/util" @@ -23,6 +25,8 @@ var ( ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid") // ErrInvalidVersion indicates an invalid version in the metadata.gz file ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") + // ErrInvalidMetadata indicates metadata that would corrupt the generated index + ErrInvalidMetadata = util.NewInvalidArgumentErrorf("package metadata is invalid") ) var globalVars = sync.OnceValue(func() (ret struct { @@ -220,6 +224,19 @@ func parseMetadataFile(r io.Reader) (*Package, error) { } } + values := []string{m.Platform} + reqs := slices.Concat(m.RequiredRubyVersion, m.RequiredRubygemsVersion) + for _, dep := range slices.Concat(m.RuntimeDependencies, m.DevelopmentDependencies) { + values = append(values, dep.Name) + reqs = append(reqs, dep.Version...) + } + for _, req := range reqs { + values = append(values, req.Restriction, req.Version) + } + if slices.ContainsFunc(values, func(s string) bool { return strings.ContainsAny(s, "\r\n") }) { + return nil, ErrInvalidMetadata // the compact index is line-based, a line break would forge entries + } + return &Package{ Name: spec.Name, Version: spec.Version.Version, diff --git a/modules/packages/rubygems/metadata_test.go b/modules/packages/rubygems/metadata_test.go index 8917bd2b36b..eba2cfd25cc 100644 --- a/modules/packages/rubygems/metadata_test.go +++ b/modules/packages/rubygems/metadata_test.go @@ -43,6 +43,19 @@ version: assert.Nil(t, rp) } }) + + t.Run("LineBreakInIndexedMetadata", func(t *testing.T) { + for _, field := range []string{ + `platform: "ruby\nx"`, + `dependencies: [{name: "d\nx"}]`, + `dependencies: [{name: d, requirement: {requirements: [[">=", {version: "1\nx"}]]}}]`, + `required_ruby_version: {requirements: [[">=", {version: "1\nx"}]]}`, + } { + rp, err := parseMetadataFile(test.CompressGzip("name: g\nversion:\n version: 1\n" + field + "\n")) + assert.ErrorIs(t, err, ErrInvalidMetadata, field) + assert.Nil(t, rp) + } + }) } func TestParseMetadataFile(t *testing.T) {