From c52e7c0cd2f7b2a97f5ce5120306793eb54781bd Mon Sep 17 00:00:00 2001 From: Basavaraj S m Date: Wed, 21 Oct 2026 02:48:44 +0530 Subject: [PATCH] fix(packages): reject newline-bearing rubygems dependency metadata (#39782) 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: 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 7075b4233e3..357a7a11663 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/packages" @@ -24,6 +26,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 { @@ -221,6 +225,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) {