!170 golang: fix CVE-2022-23806,CVE-2022-23773,CVE-2022-24921,CVE-2021-44716,CVE-2022-23772,CVE-2022-41717
From: @hcnbxx Reviewed-by: @jing-rui Signed-off-by: @jing-rui
This commit is contained in:
commit
87c488bd2f
144
0025-release-branch.go1.17-crypto-elliptic-make-IsOnCurve.patch
Normal file
144
0025-release-branch.go1.17-crypto-elliptic-make-IsOnCurve.patch
Normal file
@ -0,0 +1,144 @@
|
||||
From 041fa43ad6669ac10ccdcdd8f47653897c592dfb Mon Sep 17 00:00:00 2001
|
||||
From: Filippo Valsorda <filippo@golang.org>
|
||||
Date: Wed, 2 Feb 2022 09:14:57 -0800
|
||||
Subject: [PATCH] [release-branch.go1.17] crypto/elliptic: make IsOnCurve
|
||||
return false for invalid field elements
|
||||
|
||||
Updates #50974
|
||||
Fixes #50978
|
||||
Fixes CVE-2022-23806
|
||||
|
||||
Change-Id: I0201c2c88f13dd82910985a495973f1683af9259
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/382854
|
||||
Trust: Filippo Valsorda <filippo@golang.org>
|
||||
Run-TryBot: Filippo Valsorda <filippo@golang.org>
|
||||
Reviewed-by: Katie Hockman <katie@golang.org>
|
||||
Trust: Katie Hockman <katie@golang.org>
|
||||
TryBot-Result: Gopher Robot <gobot@golang.org>
|
||||
|
||||
Conflict: NA
|
||||
Reference: https://go-review.googlesource.com/c/go/+/382854
|
||||
---
|
||||
src/crypto/elliptic/elliptic.go | 5 +++
|
||||
src/crypto/elliptic/elliptic_test.go | 55 ++++++++++++++++++++++++++++
|
||||
src/crypto/elliptic/p224.go | 5 +++
|
||||
src/crypto/elliptic/p521.go | 5 +++
|
||||
4 files changed, 70 insertions(+)
|
||||
|
||||
diff --git a/src/crypto/elliptic/elliptic.go b/src/crypto/elliptic/elliptic.go
|
||||
index f072960bfed..b84339ec1c2 100644
|
||||
--- a/src/crypto/elliptic/elliptic.go
|
||||
+++ b/src/crypto/elliptic/elliptic.go
|
||||
@@ -86,6 +86,11 @@ func (curve *CurveParams) IsOnCurve(x, y *big.Int) bool {
|
||||
return specific.IsOnCurve(x, y)
|
||||
}
|
||||
|
||||
+ if x.Sign() < 0 || x.Cmp(curve.P) >= 0 ||
|
||||
+ y.Sign() < 0 || y.Cmp(curve.P) >= 0 {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
// y² = x³ - 3x + b
|
||||
y2 := new(big.Int).Mul(y, y)
|
||||
y2.Mod(y2, curve.P)
|
||||
diff --git a/src/crypto/elliptic/elliptic_test.go b/src/crypto/elliptic/elliptic_test.go
|
||||
index 183861a54b5..3fe53c5f332 100644
|
||||
--- a/src/crypto/elliptic/elliptic_test.go
|
||||
+++ b/src/crypto/elliptic/elliptic_test.go
|
||||
@@ -174,6 +174,61 @@ func testUnmarshalToLargeCoordinates(t *testing.T, curve Curve) {
|
||||
}
|
||||
}
|
||||
|
||||
+// TestInvalidCoordinates tests big.Int values that are not valid field elements
|
||||
+// (negative or bigger than P). They are expected to return false from
|
||||
+// IsOnCurve, all other behavior is undefined.
|
||||
+func TestInvalidCoordinates(t *testing.T) {
|
||||
+ testAllCurves(t, testInvalidCoordinates)
|
||||
+}
|
||||
+
|
||||
+func testInvalidCoordinates(t *testing.T, curve Curve) {
|
||||
+ checkIsOnCurveFalse := func(name string, x, y *big.Int) {
|
||||
+ if curve.IsOnCurve(x, y) {
|
||||
+ t.Errorf("IsOnCurve(%s) unexpectedly returned true", name)
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ p := curve.Params().P
|
||||
+ _, x, y, _ := GenerateKey(curve, rand.Reader)
|
||||
+ xx, yy := new(big.Int), new(big.Int)
|
||||
+
|
||||
+ // Check if the sign is getting dropped.
|
||||
+ xx.Neg(x)
|
||||
+ checkIsOnCurveFalse("-x, y", xx, y)
|
||||
+ yy.Neg(y)
|
||||
+ checkIsOnCurveFalse("x, -y", x, yy)
|
||||
+
|
||||
+ // Check if negative values are reduced modulo P.
|
||||
+ xx.Sub(x, p)
|
||||
+ checkIsOnCurveFalse("x-P, y", xx, y)
|
||||
+ yy.Sub(y, p)
|
||||
+ checkIsOnCurveFalse("x, y-P", x, yy)
|
||||
+
|
||||
+ // Check if positive values are reduced modulo P.
|
||||
+ xx.Add(x, p)
|
||||
+ checkIsOnCurveFalse("x+P, y", xx, y)
|
||||
+ yy.Add(y, p)
|
||||
+ checkIsOnCurveFalse("x, y+P", x, yy)
|
||||
+
|
||||
+ // Check if the overflow is dropped.
|
||||
+ xx.Add(x, new(big.Int).Lsh(big.NewInt(1), 535))
|
||||
+ checkIsOnCurveFalse("x+2⁵³⁵, y", xx, y)
|
||||
+ yy.Add(y, new(big.Int).Lsh(big.NewInt(1), 535))
|
||||
+ checkIsOnCurveFalse("x, y+2⁵³⁵", x, yy)
|
||||
+
|
||||
+ // Check if P is treated like zero (if possible).
|
||||
+ // y^2 = x^3 - 3x + B
|
||||
+ // y = mod_sqrt(x^3 - 3x + B)
|
||||
+ // y = mod_sqrt(B) if x = 0
|
||||
+ // If there is no modsqrt, there is no point with x = 0, can't test x = P.
|
||||
+ if yy := new(big.Int).ModSqrt(curve.Params().B, p); yy != nil {
|
||||
+ if !curve.IsOnCurve(big.NewInt(0), yy) {
|
||||
+ t.Fatal("(0, mod_sqrt(B)) is not on the curve?")
|
||||
+ }
|
||||
+ checkIsOnCurveFalse("P, y", p, yy)
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
func TestMarshalCompressed(t *testing.T) {
|
||||
t.Run("P-256/03", func(t *testing.T) {
|
||||
data, _ := hex.DecodeString("031e3987d9f9ea9d7dd7155a56a86b2009e1e0ab332f962d10d8beb6406ab1ad79")
|
||||
diff --git a/src/crypto/elliptic/p224.go b/src/crypto/elliptic/p224.go
|
||||
index 8c760214642..ff5c8344522 100644
|
||||
--- a/src/crypto/elliptic/p224.go
|
||||
+++ b/src/crypto/elliptic/p224.go
|
||||
@@ -48,6 +48,11 @@ func (curve p224Curve) Params() *CurveParams {
|
||||
}
|
||||
|
||||
func (curve p224Curve) IsOnCurve(bigX, bigY *big.Int) bool {
|
||||
+ if bigX.Sign() < 0 || bigX.Cmp(curve.P) >= 0 ||
|
||||
+ bigY.Sign() < 0 || bigY.Cmp(curve.P) >= 0 {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
var x, y p224FieldElement
|
||||
p224FromBig(&x, bigX)
|
||||
p224FromBig(&y, bigY)
|
||||
diff --git a/src/crypto/elliptic/p521.go b/src/crypto/elliptic/p521.go
|
||||
index 3d355943ec7..587991e31bf 100644
|
||||
--- a/src/crypto/elliptic/p521.go
|
||||
+++ b/src/crypto/elliptic/p521.go
|
||||
@@ -32,6 +32,11 @@ func (curve p521Curve) Params() *CurveParams {
|
||||
}
|
||||
|
||||
func (curve p521Curve) IsOnCurve(x, y *big.Int) bool {
|
||||
+ if x.Sign() < 0 || x.Cmp(curve.P) >= 0 ||
|
||||
+ y.Sign() < 0 || y.Cmp(curve.P) >= 0 {
|
||||
+ return false
|
||||
+ }
|
||||
+
|
||||
x1 := bigIntToFiatP521(x)
|
||||
y1 := bigIntToFiatP521(y)
|
||||
b := bigIntToFiatP521(curve.B) // TODO: precompute this value.
|
||||
--
|
||||
2.30.0
|
||||
|
||||
749
0026-release-branch.go1.17-cmd-go-internal-modfetch-do-no.patch
Normal file
749
0026-release-branch.go1.17-cmd-go-internal-modfetch-do-no.patch
Normal file
@ -0,0 +1,749 @@
|
||||
From 8d5747c8fdc3d952696505562e7804e8e5fa0ab2 Mon Sep 17 00:00:00 2001
|
||||
From: "Bryan C. Mills" <bcmills@google.com>
|
||||
Date: Thu, 13 Jan 2022 15:38:14 -0500
|
||||
Subject: [PATCH 2/6] [release-branch.go1.17] cmd/go/internal/modfetch: do not
|
||||
short-circuit canonical versions
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Since at least CL 121857, the conversion logic in
|
||||
(*modfetch).codeRepo.Stat has had a short-circuit to use the version
|
||||
requested by the caller if it successfully resolves and is already
|
||||
canonical.
|
||||
|
||||
However, we should not use that version if it refers to a branch
|
||||
instead of a tag, because branches (unlike tags) usually do not refer
|
||||
to a single, stable release: a branch named "v1.0.0" may be for the
|
||||
development of the v1.0.0 release, or for the development of patches
|
||||
based on v1.0.0, but only one commit (perhaps at the end of that
|
||||
branch — but possibly not even written yet!) can be that specific
|
||||
version.
|
||||
|
||||
We already have some logic to prefer tags that are semver-equivalent
|
||||
to the version requested by the caller. That more general case
|
||||
suffices for exact equality too — so we can eliminate the
|
||||
special-case, fixing the bug and (happily!) also somewhat simplifying
|
||||
the code.
|
||||
|
||||
Updates #35671
|
||||
Fixes #50687
|
||||
Fixes CVE-2022-23773
|
||||
|
||||
Change-Id: I2fd290190b8a99a580deec7e26d15659b58a50b0
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/378400
|
||||
Trust: Bryan Mills <bcmills@google.com>
|
||||
Run-TryBot: Bryan Mills <bcmills@google.com>
|
||||
Reviewed-by: Russ Cox <rsc@golang.org>
|
||||
TryBot-Result: Gopher Robot <gobot@golang.org>
|
||||
(cherry picked from commit fa4d9b8e2bc2612960c80474fca83a4c85a974eb)
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/382835
|
||||
|
||||
Conflict: NA
|
||||
Reference: https://go-review.googlesource.com/c/go/+/382835
|
||||
---
|
||||
src/cmd/go/internal/modfetch/coderepo.go | 216 ++++++-------
|
||||
src/cmd/go/internal/modfetch/coderepo_test.go | 301 ++++++++++--------
|
||||
.../testdata/script/mod_invalid_version.txt | 10 +-
|
||||
3 files changed, 277 insertions(+), 250 deletions(-)
|
||||
|
||||
diff --git a/src/cmd/go/internal/modfetch/coderepo.go b/src/cmd/go/internal/modfetch/coderepo.go
|
||||
index dfef9f73c27..0ee707025d0 100644
|
||||
--- a/src/cmd/go/internal/modfetch/coderepo.go
|
||||
+++ b/src/cmd/go/internal/modfetch/coderepo.go
|
||||
@@ -298,16 +298,13 @@ func (r *codeRepo) Latest() (*RevInfo, error) {
|
||||
// If statVers is a valid module version, it is used for the Version field.
|
||||
// Otherwise, the Version is derived from the passed-in info and recent tags.
|
||||
func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, error) {
|
||||
- info2 := &RevInfo{
|
||||
- Name: info.Name,
|
||||
- Short: info.Short,
|
||||
- Time: info.Time,
|
||||
- }
|
||||
-
|
||||
// If this is a plain tag (no dir/ prefix)
|
||||
// and the module path is unversioned,
|
||||
// and if the underlying file tree has no go.mod,
|
||||
// then allow using the tag with a +incompatible suffix.
|
||||
+ //
|
||||
+ // (If the version is +incompatible, then the go.mod file must not exist:
|
||||
+ // +incompatible is not an ongoing opt-out from semantic import versioning.)
|
||||
var canUseIncompatible func() bool
|
||||
canUseIncompatible = func() bool {
|
||||
var ok bool
|
||||
@@ -321,19 +318,12 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
|
||||
return ok
|
||||
}
|
||||
|
||||
- invalidf := func(format string, args ...interface{}) error {
|
||||
- return &module.ModuleError{
|
||||
- Path: r.modPath,
|
||||
- Err: &module.InvalidVersionError{
|
||||
- Version: info2.Version,
|
||||
- Err: fmt.Errorf(format, args...),
|
||||
- },
|
||||
- }
|
||||
- }
|
||||
-
|
||||
- // checkGoMod verifies that the go.mod file for the module exists or does not
|
||||
- // exist as required by info2.Version and the module path represented by r.
|
||||
- checkGoMod := func() (*RevInfo, error) {
|
||||
+ // checkCanonical verifies that the canonical version v is compatible with the
|
||||
+ // module path represented by r, adding a "+incompatible" suffix if needed.
|
||||
+ //
|
||||
+ // If statVers is also canonical, checkCanonical also verifies that v is
|
||||
+ // either statVers or statVers with the added "+incompatible" suffix.
|
||||
+ checkCanonical := func(v string) (*RevInfo, error) {
|
||||
// If r.codeDir is non-empty, then the go.mod file must exist: the module
|
||||
// author — not the module consumer, — gets to decide how to carve up the repo
|
||||
// into modules.
|
||||
@@ -344,73 +334,91 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
|
||||
// r.findDir verifies both of these conditions. Execute it now so that
|
||||
// r.Stat will correctly return a notExistError if the go.mod location or
|
||||
// declared module path doesn't match.
|
||||
- _, _, _, err := r.findDir(info2.Version)
|
||||
+ _, _, _, err := r.findDir(v)
|
||||
if err != nil {
|
||||
// TODO: It would be nice to return an error like "not a module".
|
||||
// Right now we return "missing go.mod", which is a little confusing.
|
||||
return nil, &module.ModuleError{
|
||||
Path: r.modPath,
|
||||
Err: &module.InvalidVersionError{
|
||||
- Version: info2.Version,
|
||||
+ Version: v,
|
||||
Err: notExistError{err: err},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
- // If the version is +incompatible, then the go.mod file must not exist:
|
||||
- // +incompatible is not an ongoing opt-out from semantic import versioning.
|
||||
- if strings.HasSuffix(info2.Version, "+incompatible") {
|
||||
- if !canUseIncompatible() {
|
||||
+ invalidf := func(format string, args ...interface{}) error {
|
||||
+ return &module.ModuleError{
|
||||
+ Path: r.modPath,
|
||||
+ Err: &module.InvalidVersionError{
|
||||
+ Version: v,
|
||||
+ Err: fmt.Errorf(format, args...),
|
||||
+ },
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // Add the +incompatible suffix if needed or requested explicitly, and
|
||||
+ // verify that its presence or absence is appropriate for this version
|
||||
+ // (which depends on whether it has an explicit go.mod file).
|
||||
+
|
||||
+ if v == strings.TrimSuffix(statVers, "+incompatible") {
|
||||
+ v = statVers
|
||||
+ }
|
||||
+ base := strings.TrimSuffix(v, "+incompatible")
|
||||
+ var errIncompatible error
|
||||
+ if !module.MatchPathMajor(base, r.pathMajor) {
|
||||
+ if canUseIncompatible() {
|
||||
+ v = base + "+incompatible"
|
||||
+ } else {
|
||||
if r.pathMajor != "" {
|
||||
- return nil, invalidf("+incompatible suffix not allowed: module path includes a major version suffix, so major version must match")
|
||||
+ errIncompatible = invalidf("module path includes a major version suffix, so major version must match")
|
||||
} else {
|
||||
- return nil, invalidf("+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required")
|
||||
+ errIncompatible = invalidf("module contains a go.mod file, so module path must match major version (%q)", path.Join(r.pathPrefix, semver.Major(v)))
|
||||
}
|
||||
}
|
||||
+ } else if strings.HasSuffix(v, "+incompatible") {
|
||||
+ errIncompatible = invalidf("+incompatible suffix not allowed: major version %s is compatible", semver.Major(v))
|
||||
+ }
|
||||
|
||||
- if err := module.CheckPathMajor(strings.TrimSuffix(info2.Version, "+incompatible"), r.pathMajor); err == nil {
|
||||
- return nil, invalidf("+incompatible suffix not allowed: major version %s is compatible", semver.Major(info2.Version))
|
||||
+ if statVers != "" && statVers == module.CanonicalVersion(statVers) {
|
||||
+ // Since the caller-requested version is canonical, it would be very
|
||||
+ // confusing to resolve it to anything but itself, possibly with a
|
||||
+ // "+incompatible" suffix. Error out explicitly.
|
||||
+ if statBase := strings.TrimSuffix(statVers, "+incompatible"); statBase != base {
|
||||
+ return nil, &module.ModuleError{
|
||||
+ Path: r.modPath,
|
||||
+ Err: &module.InvalidVersionError{
|
||||
+ Version: statVers,
|
||||
+ Err: fmt.Errorf("resolves to version %v (%s is not a tag)", v, statBase),
|
||||
+ },
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
- return info2, nil
|
||||
+ if errIncompatible != nil {
|
||||
+ return nil, errIncompatible
|
||||
+ }
|
||||
+
|
||||
+ return &RevInfo{
|
||||
+ Name: info.Name,
|
||||
+ Short: info.Short,
|
||||
+ Time: info.Time,
|
||||
+ Version: v,
|
||||
+ }, nil
|
||||
}
|
||||
|
||||
// Determine version.
|
||||
- //
|
||||
- // If statVers is canonical, then the original call was repo.Stat(statVers).
|
||||
- // Since the version is canonical, we must not resolve it to anything but
|
||||
- // itself, possibly with a '+incompatible' annotation: we do not need to do
|
||||
- // the work required to look for an arbitrary pseudo-version.
|
||||
- if statVers != "" && statVers == module.CanonicalVersion(statVers) {
|
||||
- info2.Version = statVers
|
||||
-
|
||||
- if module.IsPseudoVersion(info2.Version) {
|
||||
- if err := r.validatePseudoVersion(info, info2.Version); err != nil {
|
||||
- return nil, err
|
||||
- }
|
||||
- return checkGoMod()
|
||||
- }
|
||||
|
||||
- if err := module.CheckPathMajor(info2.Version, r.pathMajor); err != nil {
|
||||
- if canUseIncompatible() {
|
||||
- info2.Version += "+incompatible"
|
||||
- return checkGoMod()
|
||||
- } else {
|
||||
- if vErr, ok := err.(*module.InvalidVersionError); ok {
|
||||
- // We're going to describe why the version is invalid in more detail,
|
||||
- // so strip out the existing “invalid version” wrapper.
|
||||
- err = vErr.Err
|
||||
- }
|
||||
- return nil, invalidf("module contains a go.mod file, so major version must be compatible: %v", err)
|
||||
- }
|
||||
+ if module.IsPseudoVersion(statVers) {
|
||||
+ if err := r.validatePseudoVersion(info, statVers); err != nil {
|
||||
+ return nil, err
|
||||
}
|
||||
-
|
||||
- return checkGoMod()
|
||||
+ return checkCanonical(statVers)
|
||||
}
|
||||
|
||||
- // statVers is empty or non-canonical, so we need to resolve it to a canonical
|
||||
- // version or pseudo-version.
|
||||
+ // statVers is not a pseudo-version, so we need to either resolve it to a
|
||||
+ // canonical version or verify that it is already a canonical tag
|
||||
+ // (not a branch).
|
||||
|
||||
// Derive or verify a version from a code repo tag.
|
||||
// Tag must have a prefix matching codeDir.
|
||||
@@ -441,71 +449,62 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
|
||||
if v == "" || !strings.HasPrefix(trimmed, v) {
|
||||
return "", false // Invalid or incomplete version (just vX or vX.Y).
|
||||
}
|
||||
- if isRetracted(v) {
|
||||
- return "", false
|
||||
- }
|
||||
if v == trimmed {
|
||||
tagIsCanonical = true
|
||||
}
|
||||
-
|
||||
- if err := module.CheckPathMajor(v, r.pathMajor); err != nil {
|
||||
- if canUseIncompatible() {
|
||||
- return v + "+incompatible", tagIsCanonical
|
||||
- }
|
||||
- return "", false
|
||||
- }
|
||||
-
|
||||
return v, tagIsCanonical
|
||||
}
|
||||
|
||||
// If the VCS gave us a valid version, use that.
|
||||
if v, tagIsCanonical := tagToVersion(info.Version); tagIsCanonical {
|
||||
- info2.Version = v
|
||||
- return checkGoMod()
|
||||
+ if info, err := checkCanonical(v); err == nil {
|
||||
+ return info, err
|
||||
+ }
|
||||
}
|
||||
|
||||
// Look through the tags on the revision for either a usable canonical version
|
||||
// or an appropriate base for a pseudo-version.
|
||||
- var pseudoBase string
|
||||
+ var (
|
||||
+ highestCanonical string
|
||||
+ pseudoBase string
|
||||
+ )
|
||||
for _, pathTag := range info.Tags {
|
||||
v, tagIsCanonical := tagToVersion(pathTag)
|
||||
- if tagIsCanonical {
|
||||
- if statVers != "" && semver.Compare(v, statVers) == 0 {
|
||||
- // The user requested a non-canonical version, but the tag for the
|
||||
- // canonical equivalent refers to the same revision. Use it.
|
||||
- info2.Version = v
|
||||
- return checkGoMod()
|
||||
+ if statVers != "" && semver.Compare(v, statVers) == 0 {
|
||||
+ // The tag is equivalent to the version requested by the user.
|
||||
+ if tagIsCanonical {
|
||||
+ // This tag is the canonical form of the requested version,
|
||||
+ // not some other form with extra build metadata.
|
||||
+ // Use this tag so that the resolved version will match exactly.
|
||||
+ // (If it isn't actually allowed, we'll error out in checkCanonical.)
|
||||
+ return checkCanonical(v)
|
||||
} else {
|
||||
- // Save the highest canonical tag for the revision. If we don't find a
|
||||
- // better match, we'll use it as the canonical version.
|
||||
+ // The user explicitly requested something equivalent to this tag. We
|
||||
+ // can't use the version from the tag directly: since the tag is not
|
||||
+ // canonical, it could be ambiguous. For example, tags v0.0.1+a and
|
||||
+ // v0.0.1+b might both exist and refer to different revisions.
|
||||
//
|
||||
- // NOTE: Do not replace this with semver.Max. Despite the name,
|
||||
- // semver.Max *also* canonicalizes its arguments, which uses
|
||||
- // semver.Canonical instead of module.CanonicalVersion and thereby
|
||||
- // strips our "+incompatible" suffix.
|
||||
- if semver.Compare(info2.Version, v) < 0 {
|
||||
- info2.Version = v
|
||||
- }
|
||||
+ // The tag is otherwise valid for the module, so we can at least use it as
|
||||
+ // the base of an unambiguous pseudo-version.
|
||||
+ //
|
||||
+ // If multiple tags match, tagToVersion will canonicalize them to the same
|
||||
+ // base version.
|
||||
+ pseudoBase = v
|
||||
+ }
|
||||
+ }
|
||||
+ // Save the highest non-retracted canonical tag for the revision.
|
||||
+ // If we don't find a better match, we'll use it as the canonical version.
|
||||
+ if tagIsCanonical && semver.Compare(highestCanonical, v) < 0 && !isRetracted(v) {
|
||||
+ if module.MatchPathMajor(v, r.pathMajor) || canUseIncompatible() {
|
||||
+ highestCanonical = v
|
||||
}
|
||||
- } else if v != "" && semver.Compare(v, statVers) == 0 {
|
||||
- // The user explicitly requested something equivalent to this tag. We
|
||||
- // can't use the version from the tag directly: since the tag is not
|
||||
- // canonical, it could be ambiguous. For example, tags v0.0.1+a and
|
||||
- // v0.0.1+b might both exist and refer to different revisions.
|
||||
- //
|
||||
- // The tag is otherwise valid for the module, so we can at least use it as
|
||||
- // the base of an unambiguous pseudo-version.
|
||||
- //
|
||||
- // If multiple tags match, tagToVersion will canonicalize them to the same
|
||||
- // base version.
|
||||
- pseudoBase = v
|
||||
}
|
||||
}
|
||||
|
||||
- // If we found any canonical tag for the revision, return it.
|
||||
+ // If we found a valid canonical tag for the revision, return it.
|
||||
// Even if we found a good pseudo-version base, a canonical version is better.
|
||||
- if info2.Version != "" {
|
||||
- return checkGoMod()
|
||||
+ if highestCanonical != "" {
|
||||
+ return checkCanonical(highestCanonical)
|
||||
}
|
||||
|
||||
// Find the highest tagged version in the revision's history, subject to
|
||||
@@ -528,11 +527,10 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e
|
||||
tag, _ = r.code.RecentTag(info.Name, tagPrefix, allowedMajor("v0"))
|
||||
}
|
||||
}
|
||||
- pseudoBase, _ = tagToVersion(tag) // empty if the tag is invalid
|
||||
+ pseudoBase, _ = tagToVersion(tag)
|
||||
}
|
||||
|
||||
- info2.Version = module.PseudoVersion(r.pseudoMajor, pseudoBase, info.Time, info.Short)
|
||||
- return checkGoMod()
|
||||
+ return checkCanonical(module.PseudoVersion(r.pseudoMajor, pseudoBase, info.Time, info.Short))
|
||||
}
|
||||
|
||||
// validatePseudoVersion checks that version has a major version compatible with
|
||||
@@ -556,10 +554,6 @@ func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string)
|
||||
}
|
||||
}()
|
||||
|
||||
- if err := module.CheckPathMajor(version, r.pathMajor); err != nil {
|
||||
- return err
|
||||
- }
|
||||
-
|
||||
rev, err := module.PseudoVersionRev(version)
|
||||
if err != nil {
|
||||
return err
|
||||
diff --git a/src/cmd/go/internal/modfetch/coderepo_test.go b/src/cmd/go/internal/modfetch/coderepo_test.go
|
||||
index 02e399f3525..d98ea87da2c 100644
|
||||
--- a/src/cmd/go/internal/modfetch/coderepo_test.go
|
||||
+++ b/src/cmd/go/internal/modfetch/coderepo_test.go
|
||||
@@ -418,171 +418,204 @@ var codeRepoTests = []codeRepoTest{
|
||||
zipSum: "h1:JItBZ+gwA5WvtZEGEbuDL4lUttGtLrs53lmdurq3bOg=",
|
||||
zipFileHash: "9ea9ae1673cffcc44b7fdd3cc89953d68c102449b46c982dbf085e4f2e394da5",
|
||||
},
|
||||
+ {
|
||||
+ // Git branch with a semver name, +incompatible version, and no go.mod file.
|
||||
+ vcs: "git",
|
||||
+ path: "vcs-test.golang.org/go/mod/gitrepo1",
|
||||
+ rev: "v2.3.4+incompatible",
|
||||
+ err: `resolves to version v2.0.1+incompatible (v2.3.4 is not a tag)`,
|
||||
+ },
|
||||
+ {
|
||||
+ // Git branch with a semver name, matching go.mod file, and compatible version.
|
||||
+ vcs: "git",
|
||||
+ path: "vcs-test.golang.org/git/semver-branch.git",
|
||||
+ rev: "v1.0.0",
|
||||
+ err: `resolves to version v0.1.1-0.20220202191944-09c4d8f6938c (v1.0.0 is not a tag)`,
|
||||
+ },
|
||||
+ {
|
||||
+ // Git branch with a semver name, matching go.mod file, and disallowed +incompatible version.
|
||||
+ // The version/tag mismatch takes precedence over the +incompatible mismatched.
|
||||
+ vcs: "git",
|
||||
+ path: "vcs-test.golang.org/git/semver-branch.git",
|
||||
+ rev: "v2.0.0+incompatible",
|
||||
+ err: `resolves to version v0.1.0 (v2.0.0 is not a tag)`,
|
||||
+ },
|
||||
+ {
|
||||
+ // Git branch with a semver name, matching go.mod file, and mismatched version.
|
||||
+ // The version/tag mismatch takes precedence over the +incompatible mismatched.
|
||||
+ vcs: "git",
|
||||
+ path: "vcs-test.golang.org/git/semver-branch.git",
|
||||
+ rev: "v2.0.0",
|
||||
+ err: `resolves to version v0.1.0 (v2.0.0 is not a tag)`,
|
||||
+ },
|
||||
+ {
|
||||
+ // v3.0.0-devel is the same as tag v4.0.0-beta.1, but v4.0.0-beta.1 would
|
||||
+ // not be allowed because it is incompatible and a go.mod file exists.
|
||||
+ // The error message should refer to a valid pseudo-version, not the
|
||||
+ // unusable semver tag.
|
||||
+ vcs: "git",
|
||||
+ path: "vcs-test.golang.org/git/semver-branch.git",
|
||||
+ rev: "v3.0.0-devel",
|
||||
+ err: `resolves to version v0.1.1-0.20220203155313-d59622f6e4d7 (v3.0.0-devel is not a tag)`,
|
||||
+ },
|
||||
}
|
||||
|
||||
func TestCodeRepo(t *testing.T) {
|
||||
testenv.MustHaveExternalNetwork(t)
|
||||
+ tmpdir := t.TempDir()
|
||||
|
||||
- tmpdir, err := os.MkdirTemp("", "modfetch-test-")
|
||||
- if err != nil {
|
||||
- t.Fatal(err)
|
||||
- }
|
||||
- defer os.RemoveAll(tmpdir)
|
||||
+ for _, tt := range codeRepoTests {
|
||||
+ f := func(tt codeRepoTest) func(t *testing.T) {
|
||||
+ return func(t *testing.T) {
|
||||
+ t.Parallel()
|
||||
+ if tt.vcs != "mod" {
|
||||
+ testenv.MustHaveExecPath(t, tt.vcs)
|
||||
+ }
|
||||
|
||||
- t.Run("parallel", func(t *testing.T) {
|
||||
- for _, tt := range codeRepoTests {
|
||||
- f := func(tt codeRepoTest) func(t *testing.T) {
|
||||
- return func(t *testing.T) {
|
||||
- t.Parallel()
|
||||
- if tt.vcs != "mod" {
|
||||
- testenv.MustHaveExecPath(t, tt.vcs)
|
||||
- }
|
||||
+ repo := Lookup("direct", tt.path)
|
||||
|
||||
- repo := Lookup("direct", tt.path)
|
||||
+ if tt.mpath == "" {
|
||||
+ tt.mpath = tt.path
|
||||
+ }
|
||||
+ if mpath := repo.ModulePath(); mpath != tt.mpath {
|
||||
+ t.Errorf("repo.ModulePath() = %q, want %q", mpath, tt.mpath)
|
||||
+ }
|
||||
|
||||
- if tt.mpath == "" {
|
||||
- tt.mpath = tt.path
|
||||
- }
|
||||
- if mpath := repo.ModulePath(); mpath != tt.mpath {
|
||||
- t.Errorf("repo.ModulePath() = %q, want %q", mpath, tt.mpath)
|
||||
+ info, err := repo.Stat(tt.rev)
|
||||
+ if err != nil {
|
||||
+ if tt.err != "" {
|
||||
+ if !strings.Contains(err.Error(), tt.err) {
|
||||
+ t.Fatalf("repoStat(%q): %v, wanted %q", tt.rev, err, tt.err)
|
||||
+ }
|
||||
+ return
|
||||
}
|
||||
+ t.Fatalf("repo.Stat(%q): %v", tt.rev, err)
|
||||
+ }
|
||||
+ if tt.err != "" {
|
||||
+ t.Errorf("repo.Stat(%q): success, wanted error", tt.rev)
|
||||
+ }
|
||||
+ if info.Version != tt.version {
|
||||
+ t.Errorf("info.Version = %q, want %q", info.Version, tt.version)
|
||||
+ }
|
||||
+ if info.Name != tt.name {
|
||||
+ t.Errorf("info.Name = %q, want %q", info.Name, tt.name)
|
||||
+ }
|
||||
+ if info.Short != tt.short {
|
||||
+ t.Errorf("info.Short = %q, want %q", info.Short, tt.short)
|
||||
+ }
|
||||
+ if !info.Time.Equal(tt.time) {
|
||||
+ t.Errorf("info.Time = %v, want %v", info.Time, tt.time)
|
||||
+ }
|
||||
|
||||
- info, err := repo.Stat(tt.rev)
|
||||
- if err != nil {
|
||||
- if tt.err != "" {
|
||||
- if !strings.Contains(err.Error(), tt.err) {
|
||||
- t.Fatalf("repoStat(%q): %v, wanted %q", tt.rev, err, tt.err)
|
||||
- }
|
||||
- return
|
||||
+ if tt.gomod != "" || tt.gomodErr != "" {
|
||||
+ data, err := repo.GoMod(tt.version)
|
||||
+ if err != nil && tt.gomodErr == "" {
|
||||
+ t.Errorf("repo.GoMod(%q): %v", tt.version, err)
|
||||
+ } else if err != nil && tt.gomodErr != "" {
|
||||
+ if err.Error() != tt.gomodErr {
|
||||
+ t.Errorf("repo.GoMod(%q): %v, want %q", tt.version, err, tt.gomodErr)
|
||||
}
|
||||
- t.Fatalf("repo.Stat(%q): %v", tt.rev, err)
|
||||
- }
|
||||
- if tt.err != "" {
|
||||
- t.Errorf("repo.Stat(%q): success, wanted error", tt.rev)
|
||||
- }
|
||||
- if info.Version != tt.version {
|
||||
- t.Errorf("info.Version = %q, want %q", info.Version, tt.version)
|
||||
+ } else if tt.gomodErr != "" {
|
||||
+ t.Errorf("repo.GoMod(%q) = %q, want error %q", tt.version, data, tt.gomodErr)
|
||||
+ } else if string(data) != tt.gomod {
|
||||
+ t.Errorf("repo.GoMod(%q) = %q, want %q", tt.version, data, tt.gomod)
|
||||
}
|
||||
- if info.Name != tt.name {
|
||||
- t.Errorf("info.Name = %q, want %q", info.Name, tt.name)
|
||||
- }
|
||||
- if info.Short != tt.short {
|
||||
- t.Errorf("info.Short = %q, want %q", info.Short, tt.short)
|
||||
+ }
|
||||
+
|
||||
+ needHash := !testing.Short() && (tt.zipFileHash != "" || tt.zipSum != "")
|
||||
+ if tt.zip != nil || tt.zipErr != "" || needHash {
|
||||
+ f, err := os.CreateTemp(tmpdir, tt.version+".zip.")
|
||||
+ if err != nil {
|
||||
+ t.Fatalf("os.CreateTemp: %v", err)
|
||||
}
|
||||
- if !info.Time.Equal(tt.time) {
|
||||
- t.Errorf("info.Time = %v, want %v", info.Time, tt.time)
|
||||
+ zipfile := f.Name()
|
||||
+ defer func() {
|
||||
+ f.Close()
|
||||
+ os.Remove(zipfile)
|
||||
+ }()
|
||||
+
|
||||
+ var w io.Writer
|
||||
+ var h hash.Hash
|
||||
+ if needHash {
|
||||
+ h = sha256.New()
|
||||
+ w = io.MultiWriter(f, h)
|
||||
+ } else {
|
||||
+ w = f
|
||||
}
|
||||
-
|
||||
- if tt.gomod != "" || tt.gomodErr != "" {
|
||||
- data, err := repo.GoMod(tt.version)
|
||||
- if err != nil && tt.gomodErr == "" {
|
||||
- t.Errorf("repo.GoMod(%q): %v", tt.version, err)
|
||||
- } else if err != nil && tt.gomodErr != "" {
|
||||
- if err.Error() != tt.gomodErr {
|
||||
- t.Errorf("repo.GoMod(%q): %v, want %q", tt.version, err, tt.gomodErr)
|
||||
+ err = repo.Zip(w, tt.version)
|
||||
+ f.Close()
|
||||
+ if err != nil {
|
||||
+ if tt.zipErr != "" {
|
||||
+ if err.Error() == tt.zipErr {
|
||||
+ return
|
||||
}
|
||||
- } else if tt.gomodErr != "" {
|
||||
- t.Errorf("repo.GoMod(%q) = %q, want error %q", tt.version, data, tt.gomodErr)
|
||||
- } else if string(data) != tt.gomod {
|
||||
- t.Errorf("repo.GoMod(%q) = %q, want %q", tt.version, data, tt.gomod)
|
||||
+ t.Fatalf("repo.Zip(%q): %v, want error %q", tt.version, err, tt.zipErr)
|
||||
}
|
||||
+ t.Fatalf("repo.Zip(%q): %v", tt.version, err)
|
||||
+ }
|
||||
+ if tt.zipErr != "" {
|
||||
+ t.Errorf("repo.Zip(%q): success, want error %q", tt.version, tt.zipErr)
|
||||
}
|
||||
|
||||
- needHash := !testing.Short() && (tt.zipFileHash != "" || tt.zipSum != "")
|
||||
- if tt.zip != nil || tt.zipErr != "" || needHash {
|
||||
- f, err := os.CreateTemp(tmpdir, tt.version+".zip.")
|
||||
+ if tt.zip != nil {
|
||||
+ prefix := tt.path + "@" + tt.version + "/"
|
||||
+ z, err := zip.OpenReader(zipfile)
|
||||
if err != nil {
|
||||
- t.Fatalf("os.CreateTemp: %v", err)
|
||||
+ t.Fatalf("open zip %s: %v", zipfile, err)
|
||||
}
|
||||
- zipfile := f.Name()
|
||||
- defer func() {
|
||||
- f.Close()
|
||||
- os.Remove(zipfile)
|
||||
- }()
|
||||
-
|
||||
- var w io.Writer
|
||||
- var h hash.Hash
|
||||
- if needHash {
|
||||
- h = sha256.New()
|
||||
- w = io.MultiWriter(f, h)
|
||||
- } else {
|
||||
- w = f
|
||||
- }
|
||||
- err = repo.Zip(w, tt.version)
|
||||
- f.Close()
|
||||
- if err != nil {
|
||||
- if tt.zipErr != "" {
|
||||
- if err.Error() == tt.zipErr {
|
||||
- return
|
||||
- }
|
||||
- t.Fatalf("repo.Zip(%q): %v, want error %q", tt.version, err, tt.zipErr)
|
||||
+ var names []string
|
||||
+ for _, file := range z.File {
|
||||
+ if !strings.HasPrefix(file.Name, prefix) {
|
||||
+ t.Errorf("zip entry %v does not start with prefix %v", file.Name, prefix)
|
||||
+ continue
|
||||
}
|
||||
- t.Fatalf("repo.Zip(%q): %v", tt.version, err)
|
||||
- }
|
||||
- if tt.zipErr != "" {
|
||||
- t.Errorf("repo.Zip(%q): success, want error %q", tt.version, tt.zipErr)
|
||||
+ names = append(names, file.Name[len(prefix):])
|
||||
}
|
||||
-
|
||||
- if tt.zip != nil {
|
||||
- prefix := tt.path + "@" + tt.version + "/"
|
||||
- z, err := zip.OpenReader(zipfile)
|
||||
- if err != nil {
|
||||
- t.Fatalf("open zip %s: %v", zipfile, err)
|
||||
- }
|
||||
- var names []string
|
||||
- for _, file := range z.File {
|
||||
- if !strings.HasPrefix(file.Name, prefix) {
|
||||
- t.Errorf("zip entry %v does not start with prefix %v", file.Name, prefix)
|
||||
- continue
|
||||
- }
|
||||
- names = append(names, file.Name[len(prefix):])
|
||||
- }
|
||||
- z.Close()
|
||||
- if !reflect.DeepEqual(names, tt.zip) {
|
||||
- t.Fatalf("zip = %v\nwant %v\n", names, tt.zip)
|
||||
- }
|
||||
+ z.Close()
|
||||
+ if !reflect.DeepEqual(names, tt.zip) {
|
||||
+ t.Fatalf("zip = %v\nwant %v\n", names, tt.zip)
|
||||
}
|
||||
+ }
|
||||
|
||||
- if needHash {
|
||||
- sum, err := dirhash.HashZip(zipfile, dirhash.Hash1)
|
||||
- if err != nil {
|
||||
- t.Errorf("repo.Zip(%q): %v", tt.version, err)
|
||||
- } else if sum != tt.zipSum {
|
||||
- t.Errorf("repo.Zip(%q): got file with sum %q, want %q", tt.version, sum, tt.zipSum)
|
||||
- } else if zipFileHash := hex.EncodeToString(h.Sum(nil)); zipFileHash != tt.zipFileHash {
|
||||
- t.Errorf("repo.Zip(%q): got file with hash %q, want %q (but content has correct sum)", tt.version, zipFileHash, tt.zipFileHash)
|
||||
- }
|
||||
+ if needHash {
|
||||
+ sum, err := dirhash.HashZip(zipfile, dirhash.Hash1)
|
||||
+ if err != nil {
|
||||
+ t.Errorf("repo.Zip(%q): %v", tt.version, err)
|
||||
+ } else if sum != tt.zipSum {
|
||||
+ t.Errorf("repo.Zip(%q): got file with sum %q, want %q", tt.version, sum, tt.zipSum)
|
||||
+ } else if zipFileHash := hex.EncodeToString(h.Sum(nil)); zipFileHash != tt.zipFileHash {
|
||||
+ t.Errorf("repo.Zip(%q): got file with hash %q, want %q (but content has correct sum)", tt.version, zipFileHash, tt.zipFileHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
- t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.rev, f(tt))
|
||||
- if strings.HasPrefix(tt.path, vgotest1git) {
|
||||
- for vcs, alt := range altVgotests {
|
||||
- altTest := tt
|
||||
- altTest.vcs = vcs
|
||||
- altTest.path = alt + strings.TrimPrefix(altTest.path, vgotest1git)
|
||||
- if strings.HasPrefix(altTest.mpath, vgotest1git) {
|
||||
- altTest.mpath = alt + strings.TrimPrefix(altTest.mpath, vgotest1git)
|
||||
- }
|
||||
- var m map[string]string
|
||||
- if alt == vgotest1hg {
|
||||
- m = hgmap
|
||||
- }
|
||||
- altTest.version = remap(altTest.version, m)
|
||||
- altTest.name = remap(altTest.name, m)
|
||||
- altTest.short = remap(altTest.short, m)
|
||||
- altTest.rev = remap(altTest.rev, m)
|
||||
- altTest.err = remap(altTest.err, m)
|
||||
- altTest.gomodErr = remap(altTest.gomodErr, m)
|
||||
- altTest.zipErr = remap(altTest.zipErr, m)
|
||||
- altTest.zipSum = ""
|
||||
- altTest.zipFileHash = ""
|
||||
- t.Run(strings.ReplaceAll(altTest.path, "/", "_")+"/"+altTest.rev, f(altTest))
|
||||
+ }
|
||||
+ t.Run(strings.ReplaceAll(tt.path, "/", "_")+"/"+tt.rev, f(tt))
|
||||
+ if strings.HasPrefix(tt.path, vgotest1git) {
|
||||
+ for vcs, alt := range altVgotests {
|
||||
+ altTest := tt
|
||||
+ altTest.vcs = vcs
|
||||
+ altTest.path = alt + strings.TrimPrefix(altTest.path, vgotest1git)
|
||||
+ if strings.HasPrefix(altTest.mpath, vgotest1git) {
|
||||
+ altTest.mpath = alt + strings.TrimPrefix(altTest.mpath, vgotest1git)
|
||||
+ }
|
||||
+ var m map[string]string
|
||||
+ if alt == vgotest1hg {
|
||||
+ m = hgmap
|
||||
}
|
||||
+ altTest.version = remap(altTest.version, m)
|
||||
+ altTest.name = remap(altTest.name, m)
|
||||
+ altTest.short = remap(altTest.short, m)
|
||||
+ altTest.rev = remap(altTest.rev, m)
|
||||
+ altTest.err = remap(altTest.err, m)
|
||||
+ altTest.gomodErr = remap(altTest.gomodErr, m)
|
||||
+ altTest.zipErr = remap(altTest.zipErr, m)
|
||||
+ altTest.zipSum = ""
|
||||
+ altTest.zipFileHash = ""
|
||||
+ t.Run(strings.ReplaceAll(altTest.path, "/", "_")+"/"+altTest.rev, f(altTest))
|
||||
}
|
||||
}
|
||||
- })
|
||||
+ }
|
||||
}
|
||||
|
||||
var hgmap = map[string]string{
|
||||
diff --git a/src/cmd/go/testdata/script/mod_invalid_version.txt b/src/cmd/go/testdata/script/mod_invalid_version.txt
|
||||
index 6846a792a5d..d16991dd416 100644
|
||||
--- a/src/cmd/go/testdata/script/mod_invalid_version.txt
|
||||
+++ b/src/cmd/go/testdata/script/mod_invalid_version.txt
|
||||
@@ -194,10 +194,10 @@ cp go.mod.orig go.mod
|
||||
go mod edit -require github.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d+incompatible
|
||||
cd outside
|
||||
! go list -m github.com/pierrec/lz4
|
||||
-stderr 'go list -m: example.com@v0.0.0 requires\n\tgithub.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required'
|
||||
+stderr '^go list -m: example.com@v0.0.0 requires\n\tgithub.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
|
||||
cd ..
|
||||
! go list -m github.com/pierrec/lz4
|
||||
-stderr 'github.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required'
|
||||
+stderr '^go list -m: github.com/pierrec/lz4@v2.0.9-0.20190209155647-9a39efadad3d\+incompatible: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
|
||||
|
||||
# A +incompatible pseudo-version is valid for a revision of the module
|
||||
# that lacks a go.mod file.
|
||||
@@ -222,7 +222,7 @@ stdout 'github.com/pierrec/lz4 v2.0.5\+incompatible'
|
||||
# not resolve to a pseudo-version with a different major version.
|
||||
cp go.mod.orig go.mod
|
||||
! go get -d github.com/pierrec/lz4@v2.0.8
|
||||
-stderr 'go get: github.com/pierrec/lz4@v2.0.8: invalid version: module contains a go.mod file, so major version must be compatible: should be v0 or v1, not v2'
|
||||
+stderr 'go get: github.com/pierrec/lz4@v2.0.8: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
|
||||
|
||||
# An invalid +incompatible suffix for a canonical version should error out,
|
||||
# not resolve to a pseudo-version.
|
||||
@@ -233,10 +233,10 @@ cp go.mod.orig go.mod
|
||||
go mod edit -require github.com/pierrec/lz4@v2.0.8+incompatible
|
||||
cd outside
|
||||
! go list -m github.com/pierrec/lz4
|
||||
-stderr 'github.com/pierrec/lz4@v2.0.8\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required'
|
||||
+stderr '^go list -m: github.com/pierrec/lz4@v2.0.8\+incompatible: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
|
||||
cd ..
|
||||
! go list -m github.com/pierrec/lz4
|
||||
-stderr 'github.com/pierrec/lz4@v2.0.8\+incompatible: invalid version: \+incompatible suffix not allowed: module contains a go.mod file, so semantic import versioning is required'
|
||||
+stderr '^go list -m: github.com/pierrec/lz4@v2.0.8\+incompatible: invalid version: module contains a go.mod file, so module path must match major version \("github.com/pierrec/lz4/v2"\)$'
|
||||
|
||||
-- go.mod.orig --
|
||||
module example.com
|
||||
--
|
||||
2.30.0
|
||||
|
||||
119
0027-release-branch.go1.17-regexp-syntax-reject-very-deep.patch
Normal file
119
0027-release-branch.go1.17-regexp-syntax-reject-very-deep.patch
Normal file
@ -0,0 +1,119 @@
|
||||
From 94fbe72e320175d2655c04bee76974941edb2491 Mon Sep 17 00:00:00 2001
|
||||
From: Russ Cox <rsc@golang.org>
|
||||
Date: Wed, 2 Feb 2022 16:41:32 -0500
|
||||
Subject: [PATCH] regexp/syntax: reject very deeply nested regexps in Parse
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
The regexp code assumes it can recurse over the structure of
|
||||
a regexp safely. Go's growable stacks make that reasonable
|
||||
for all plausible regexps, but implausible ones can reach the
|
||||
“infinite recursion?” stack limit.
|
||||
|
||||
This CL limits the depth of any parsed regexp to 1000.
|
||||
That is, the depth of the parse tree is required to be ≤ 1000.
|
||||
Regexps that require deeper parse trees will return ErrInternalError.
|
||||
A future CL will change the error to ErrInvalidDepth,
|
||||
but using ErrInternalError for now avoids introducing new API
|
||||
in point releases when this is backported.
|
||||
|
||||
Fixes #51112.
|
||||
Fixes #51118.
|
||||
|
||||
Change-Id: I97d2cd82195946eb43a4ea8561f5b95f91fb14c5
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/384616
|
||||
Trust: Russ Cox <rsc@golang.org>
|
||||
Run-TryBot: Russ Cox <rsc@golang.org>
|
||||
Reviewed-by: Ian Lance Taylor <iant@golang.org>
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/384854
|
||||
TryBot-Result: Gopher Robot <gobot@golang.org>
|
||||
|
||||
Conflict: NA
|
||||
Reference: https://go-review.googlesource.com/c/go/+/384854/
|
||||
---
|
||||
src/regexp/syntax/parse.go | 19 ++++++-------------
|
||||
src/regexp/syntax/parse_test.go | 5 +++++
|
||||
2 files changed, 11 insertions(+), 13 deletions(-)
|
||||
|
||||
diff --git a/src/regexp/syntax/parse.go b/src/regexp/syntax/parse.go
|
||||
index 67254d6..3792960 100644
|
||||
--- a/src/regexp/syntax/parse.go
|
||||
+++ b/src/regexp/syntax/parse.go
|
||||
@@ -43,7 +43,6 @@ const (
|
||||
ErrMissingRepeatArgument ErrorCode = "missing argument to repetition operator"
|
||||
ErrTrailingBackslash ErrorCode = "trailing backslash at end of expression"
|
||||
ErrUnexpectedParen ErrorCode = "unexpected )"
|
||||
- ErrNestingDepth ErrorCode = "expression nests too deeply"
|
||||
)
|
||||
|
||||
func (e ErrorCode) String() string {
|
||||
@@ -266,7 +265,7 @@ func (p *parser) checkHeight(re *Regexp) {
|
||||
}
|
||||
}
|
||||
if p.calcHeight(re, true) > maxHeight {
|
||||
- panic(ErrNestingDepth)
|
||||
+ panic(ErrInternalError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -578,16 +577,12 @@ func (p *parser) collapse(subs []*Regexp, op Op) *Regexp {
|
||||
// frees (passes to p.reuse) any removed *Regexps.
|
||||
//
|
||||
// For example,
|
||||
-//
|
||||
-// ABC|ABD|AEF|BCX|BCY
|
||||
-//
|
||||
+// ABC|ABD|AEF|BCX|BCY
|
||||
// simplifies by literal prefix extraction to
|
||||
-//
|
||||
-// A(B(C|D)|EF)|BC(X|Y)
|
||||
-//
|
||||
+// A(B(C|D)|EF)|BC(X|Y)
|
||||
// which simplifies by character class introduction to
|
||||
+// A(B[CD]|EF)|BC[XY]
|
||||
//
|
||||
-// A(B[CD]|EF)|BC[XY]
|
||||
func (p *parser) factor(sub []*Regexp) []*Regexp {
|
||||
if len(sub) < 2 {
|
||||
return sub
|
||||
@@ -897,10 +892,8 @@ func parse(s string, flags Flags) (_ *Regexp, err error) {
|
||||
panic(r)
|
||||
case nil:
|
||||
// ok
|
||||
- case ErrInternalError: // too big
|
||||
+ case ErrInternalError:
|
||||
err = &Error{Code: ErrInternalError, Expr: s}
|
||||
- case ErrNestingDepth:
|
||||
- err = &Error{Code: ErrNestingDepth, Expr: s}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -1943,7 +1936,7 @@ func appendClass(r []rune, x []rune) []rune {
|
||||
return r
|
||||
}
|
||||
|
||||
-// appendFoldedClass returns the result of appending the case folding of the class x to the class r.
|
||||
+// appendFolded returns the result of appending the case folding of the class x to the class r.
|
||||
func appendFoldedClass(r []rune, x []rune) []rune {
|
||||
for i := 0; i < len(x); i += 2 {
|
||||
r = appendFoldedRange(r, x[i], x[i+1])
|
||||
diff --git a/src/regexp/syntax/parse_test.go b/src/regexp/syntax/parse_test.go
|
||||
index 6044da6..67e3c56 100644
|
||||
--- a/src/regexp/syntax/parse_test.go
|
||||
+++ b/src/regexp/syntax/parse_test.go
|
||||
@@ -207,6 +207,11 @@ var parseTests = []parseTest{
|
||||
// Valid repetitions.
|
||||
{`((((((((((x{2}){2}){2}){2}){2}){2}){2}){2}){2}))`, ``},
|
||||
{`((((((((((x{1}){2}){2}){2}){2}){2}){2}){2}){2}){2})`, ``},
|
||||
+
|
||||
+ // Valid nesting.
|
||||
+ {strings.Repeat("(", 999) + strings.Repeat(")", 999), ``},
|
||||
+ {strings.Repeat("(?:", 999) + strings.Repeat(")*", 999), ``},
|
||||
+ {"(" + strings.Repeat("|", 12345) + ")", ``}, // not nested at all
|
||||
}
|
||||
|
||||
const testFlags = MatchNL | PerlX | UnicodeGroups
|
||||
--
|
||||
2.33.0
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
From 9b62e5e5e979905fd53919dfa4ad53458574ee61 Mon Sep 17 00:00:00 2001
|
||||
From: Filippo Valsorda <filippo@golang.org>
|
||||
Date: Thu, 9 Dec 2021 06:32:14 -0500
|
||||
Subject: [PATCH 4/6] [release-branch.go1.17] net/http: update bundled
|
||||
golang.org/x/net/http2
|
||||
|
||||
Pull in security fix
|
||||
|
||||
84cba54 http2: cap the size of the server's canonical header cache
|
||||
|
||||
Updates #50058
|
||||
Fixes CVE-2021-44716
|
||||
|
||||
Change-Id: Ia89e3d22a173c6cb83f03608d5186fcd08f2956c
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/370574
|
||||
Trust: Filippo Valsorda <filippo@golang.org>
|
||||
Run-TryBot: Filippo Valsorda <filippo@golang.org>
|
||||
Reviewed-by: Alex Rakoczy <alex@golang.org>
|
||||
TryBot-Result: Gopher Robot <gobot@golang.org>
|
||||
|
||||
Conflict: NA
|
||||
Reference: https://go-review.googlesource.com/c/go/+/370574
|
||||
---
|
||||
src/go.mod | 2 +-
|
||||
src/go.sum | 4 ++--
|
||||
src/net/http/h2_bundle.go | 10 +++++++++-
|
||||
src/vendor/modules.txt | 2 +-
|
||||
4 files changed, 13 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/src/go.mod b/src/go.mod
|
||||
index 386b51a6569..ada50077937 100644
|
||||
--- a/src/go.mod
|
||||
+++ b/src/go.mod
|
||||
@@ -4,7 +4,7 @@ go 1.17
|
||||
|
||||
require (
|
||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e
|
||||
- golang.org/x/net v0.0.0-20211101194204-95aca89e93de
|
||||
+ golang.org/x/net v0.0.0-20211209100829-84cba5454caf
|
||||
)
|
||||
|
||||
require (
|
||||
diff --git a/src/go.sum b/src/go.sum
|
||||
index 1f328206ecb..3e181c992f5 100644
|
||||
--- a/src/go.sum
|
||||
+++ b/src/go.sum
|
||||
@@ -1,8 +1,8 @@
|
||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e h1:8foAy0aoO5GkqCvAEJ4VC4P3zksTg4X4aJCDpZzmgQI=
|
||||
golang.org/x/crypto v0.0.0-20210503195802-e9a32991a82e/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
-golang.org/x/net v0.0.0-20211101194204-95aca89e93de h1:dKoXPECQZ51dGVSkuiD9YzeNpLT4UPUY4d3xo0sWrkU=
|
||||
-golang.org/x/net v0.0.0-20211101194204-95aca89e93de/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
+golang.org/x/net v0.0.0-20211209100829-84cba5454caf h1:Chci/BE/+xVqrcWnObL99NS8gtXyJrhHDlygBQrggHM=
|
||||
+golang.org/x/net v0.0.0-20211209100829-84cba5454caf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210511113859-b0526f3d8744 h1:yhBbb4IRs2HS9PPlAg6DMC6mUOKexJBNsLf4Z+6En1Q=
|
||||
diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go
|
||||
index 9112079a224..1b73da7f219 100644
|
||||
--- a/src/net/http/h2_bundle.go
|
||||
+++ b/src/net/http/h2_bundle.go
|
||||
@@ -4382,7 +4382,15 @@ func (sc *http2serverConn) canonicalHeader(v string) string {
|
||||
sc.canonHeader = make(map[string]string)
|
||||
}
|
||||
cv = CanonicalHeaderKey(v)
|
||||
- sc.canonHeader[v] = cv
|
||||
+ // maxCachedCanonicalHeaders is an arbitrarily-chosen limit on the number of
|
||||
+ // entries in the canonHeader cache. This should be larger than the number
|
||||
+ // of unique, uncommon header keys likely to be sent by the peer, while not
|
||||
+ // so high as to permit unreaasonable memory usage if the peer sends an unbounded
|
||||
+ // number of unique header keys.
|
||||
+ const maxCachedCanonicalHeaders = 32
|
||||
+ if len(sc.canonHeader) < maxCachedCanonicalHeaders {
|
||||
+ sc.canonHeader[v] = cv
|
||||
+ }
|
||||
return cv
|
||||
}
|
||||
|
||||
diff --git a/src/vendor/modules.txt b/src/vendor/modules.txt
|
||||
index f61fc51ba82..bb0b4c561da 100644
|
||||
--- a/src/vendor/modules.txt
|
||||
+++ b/src/vendor/modules.txt
|
||||
@@ -8,7 +8,7 @@ golang.org/x/crypto/curve25519
|
||||
golang.org/x/crypto/hkdf
|
||||
golang.org/x/crypto/internal/subtle
|
||||
golang.org/x/crypto/poly1305
|
||||
-# golang.org/x/net v0.0.0-20211101194204-95aca89e93de
|
||||
+# golang.org/x/net v0.0.0-20211209100829-84cba5454caf
|
||||
## explicit; go 1.17
|
||||
golang.org/x/net/dns/dnsmessage
|
||||
golang.org/x/net/http/httpguts
|
||||
--
|
||||
2.30.0
|
||||
|
||||
@ -0,0 +1,65 @@
|
||||
From 174d5787fa52ec66aa29ee20a04b47d98d458082 Mon Sep 17 00:00:00 2001
|
||||
From: Katie Hockman <katie@golang.org>
|
||||
Date: Wed, 19 Jan 2022 16:54:41 -0500
|
||||
Subject: [PATCH 6/6] [release-branch.go1.17] math/big: prevent overflow in
|
||||
(*Rat).SetString
|
||||
|
||||
Credit to rsc@ for the original patch.
|
||||
|
||||
Thanks to the OSS-Fuzz project for discovering this
|
||||
issue and to Emmanuel Odeke (@odeke_et) for reporting it.
|
||||
|
||||
Updates #50699
|
||||
Fixes #50701
|
||||
Fixes CVE-2022-23772
|
||||
|
||||
Change-Id: I590395a3d55689625390cf1e58f5f40623b26ee5
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/379537
|
||||
Trust: Katie Hockman <katie@golang.org>
|
||||
Run-TryBot: Katie Hockman <katie@golang.org>
|
||||
TryBot-Result: Gopher Robot <gobot@golang.org>
|
||||
Reviewed-by: Emmanuel Odeke <emmanuel@orijtech.com>
|
||||
Reviewed-by: Roland Shoemaker <roland@golang.org>
|
||||
Reviewed-by: Julie Qiu <julie@golang.org>
|
||||
(cherry picked from commit ad345c265916bbf6c646865e4642eafce6d39e78)
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/381336
|
||||
Reviewed-by: Filippo Valsorda <filippo@golang.org>
|
||||
|
||||
Conflict: NA
|
||||
Reference: https://go-review.googlesource.com/c/go/+/381336
|
||||
---
|
||||
src/math/big/ratconv.go | 5 +++++
|
||||
src/math/big/ratconv_test.go | 1 +
|
||||
2 files changed, 6 insertions(+)
|
||||
|
||||
diff --git a/src/math/big/ratconv.go b/src/math/big/ratconv.go
|
||||
index ac3c8bd11f8..90053a9c81d 100644
|
||||
--- a/src/math/big/ratconv.go
|
||||
+++ b/src/math/big/ratconv.go
|
||||
@@ -169,6 +169,11 @@ func (z *Rat) SetString(s string) (*Rat, bool) {
|
||||
n := exp5
|
||||
if n < 0 {
|
||||
n = -n
|
||||
+ if n < 0 {
|
||||
+ // This can occur if -n overflows. -(-1 << 63) would become
|
||||
+ // -1 << 63, which is still negative.
|
||||
+ return nil, false
|
||||
+ }
|
||||
}
|
||||
if n > 1e6 {
|
||||
return nil, false // avoid excessively large exponents
|
||||
diff --git a/src/math/big/ratconv_test.go b/src/math/big/ratconv_test.go
|
||||
index 15d206cb386..e55e6557189 100644
|
||||
--- a/src/math/big/ratconv_test.go
|
||||
+++ b/src/math/big/ratconv_test.go
|
||||
@@ -104,6 +104,7 @@ var setStringTests = []StringTest{
|
||||
{in: "4/3/"},
|
||||
{in: "4/3."},
|
||||
{in: "4/"},
|
||||
+ {in: "13e-9223372036854775808"}, // CVE-2022-23772
|
||||
|
||||
// valid
|
||||
{"0", "0", true},
|
||||
--
|
||||
2.30.0
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
From 15ffef9628c700156c3149d02fd27ea3409a4eb7 Mon Sep 17 00:00:00 2001
|
||||
From: Damien Neil <dneil@google.com>
|
||||
Date: Wed, 30 Nov 2022 16:37:07 -0500
|
||||
Subject: [PATCH] [release-branch.go1.18] net/http: update bundled
|
||||
golang.org/x/net/http2
|
||||
|
||||
Disable cmd/internal/moddeps test, since this update includes PRIVATE
|
||||
track fixes.
|
||||
|
||||
For #56350
|
||||
For #57008
|
||||
Fixes CVE-2022-41717
|
||||
|
||||
Change-Id: I31ebd2b9ae190ef6f7646187103ea1c8a713ff2e
|
||||
Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1663833
|
||||
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
|
||||
Reviewed-by: Julie Qiu <julieqiu@google.com>
|
||||
Reviewed-on: https://go-review.googlesource.com/c/go/+/455361
|
||||
Run-TryBot: Jenny Rakoczy <jenny@golang.org>
|
||||
Reviewed-by: Michael Pratt <mpratt@google.com>
|
||||
TryBot-Result: Gopher Robot <gobot@golang.org>
|
||||
|
||||
Reference:https://go-review.googlesource.com/c/go/+/455361
|
||||
Conflict: NA
|
||||
|
||||
---
|
||||
src/net/http/h2_bundle.go | 18 +++++++++++-------
|
||||
1 file changed, 11 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go
|
||||
index d7e2f764c8..5433ebdc51 100644
|
||||
--- a/src/net/http/h2_bundle.go
|
||||
+++ b/src/net/http/h2_bundle.go
|
||||
@@ -4189,6 +4189,7 @@ type http2serverConn struct {
|
||||
headerTableSize uint32
|
||||
peerMaxHeaderListSize uint32 // zero means unknown (default)
|
||||
canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
|
||||
+ canonHeaderKeysSize int // canonHeader keys size in bytes
|
||||
writingFrame bool // started writing a frame (on serve goroutine or separate)
|
||||
writingFrameAsync bool // started a frame on its own goroutine but haven't heard back on wroteFrameCh
|
||||
needsFrameFlush bool // last frame write wasn't a flush
|
||||
@@ -4368,6 +4369,13 @@ func (sc *http2serverConn) condlogf(err error, format string, args ...interface{
|
||||
}
|
||||
}
|
||||
|
||||
+// maxCachedCanonicalHeadersKeysSize is an arbitrarily-chosen limit on the size
|
||||
+// of the entries in the canonHeader cache.
|
||||
+// This should be larger than the size of unique, uncommon header keys likely to
|
||||
+// be sent by the peer, while not so high as to permit unreasonable memory usage
|
||||
+// if the peer sends an unbounded number of unique header keys.
|
||||
+const http2maxCachedCanonicalHeadersKeysSize = 2048
|
||||
+
|
||||
func (sc *http2serverConn) canonicalHeader(v string) string {
|
||||
sc.serveG.check()
|
||||
http2buildCommonHeaderMapsOnce()
|
||||
@@ -4383,14 +4391,10 @@ func (sc *http2serverConn) canonicalHeader(v string) string {
|
||||
sc.canonHeader = make(map[string]string)
|
||||
}
|
||||
cv = CanonicalHeaderKey(v)
|
||||
- // maxCachedCanonicalHeaders is an arbitrarily-chosen limit on the number of
|
||||
- // entries in the canonHeader cache. This should be larger than the number
|
||||
- // of unique, uncommon header keys likely to be sent by the peer, while not
|
||||
- // so high as to permit unreaasonable memory usage if the peer sends an unbounded
|
||||
- // number of unique header keys.
|
||||
- const maxCachedCanonicalHeaders = 32
|
||||
- if len(sc.canonHeader) < maxCachedCanonicalHeaders {
|
||||
+ size := 100 + len(v)*2 // 100 bytes of map overhead + key + value
|
||||
+ if sc.canonHeaderKeysSize+size <= http2maxCachedCanonicalHeadersKeysSize {
|
||||
sc.canonHeader[v] = cv
|
||||
+ sc.canonHeaderKeysSize += size
|
||||
}
|
||||
return cv
|
||||
}
|
||||
--
|
||||
2.33.0
|
||||
|
||||
14
golang.spec
14
golang.spec
@ -63,7 +63,7 @@
|
||||
|
||||
Name: golang
|
||||
Version: 1.17.3
|
||||
Release: 13
|
||||
Release: 14
|
||||
Summary: The Go Programming Language
|
||||
License: BSD and Public Domain
|
||||
URL: https://golang.org/
|
||||
@ -174,6 +174,12 @@ Patch6021: 0021-release-branch.go1.18-net-http-httputil-avoid-query-.patch
|
||||
Patch6022: 0022-release-branch.go1.18-archive-tar-limit-size-of-head.patch
|
||||
Patch6023: 0023-syscall-os-exec-reject-environment-variables-contain.patch
|
||||
Patch6024: 0024-release-branch.go1.18-add-definition-byte-string-cut.patch
|
||||
Patch6025: 0025-release-branch.go1.17-crypto-elliptic-make-IsOnCurve.patch
|
||||
Patch6026: 0026-release-branch.go1.17-cmd-go-internal-modfetch-do-no.patch
|
||||
Patch6027: 0027-release-branch.go1.17-regexp-syntax-reject-very-deep.patch
|
||||
Patch6028: 0028-release-branch.go1.17-net-http-update-bundled-golang.patch
|
||||
Patch6029: 0029-release-branch.go1.17-math-big-prevent-overflow-in-R.patch
|
||||
Patch6030: 0030-release-branch.go1.18-net-http-update-bundled-golang.patch
|
||||
|
||||
ExclusiveArch: %{golang_arches}
|
||||
|
||||
@ -412,6 +418,12 @@ fi
|
||||
%files devel -f go-tests.list -f go-misc.list -f go-src.list
|
||||
|
||||
%changelog
|
||||
* Fri Jan 20 2023 hanchao <hanchao47@huawei.com> - 1.17.3-14
|
||||
- Type:CVE
|
||||
- CVE:CVE-2022-23806,CVE-2022-23773,CVE-2022-24921,CVE-2021-44716,CVE-2022-23772,CVE-2022-41717
|
||||
- SUG:NA
|
||||
- DESC: fix CVE-2022-23806,CVE-2022-23773,CVE-2022-24921,CVE-2021-44716,CVE-2022-23772,CVE-2022-41717
|
||||
|
||||
* Sat Dec 17 2022 wanglimin<wanglimin@xfusion.com> - 1.17.3-13
|
||||
- Add string cut
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user