Package: release.debian.org Severity: normal Tags: bullseye User: release.debian....@packages.debian.org Usertags: pu X-Debbugs-Cc: z...@debian.org, t...@security.debian.org
[ Reason ] Backport patches for CVE-2022-23806 CVE-2022-23772 CVE-2022-23773 [ Impact ] + CVE-2022-23806: crypto/elliptic: fix IsOnCurve for big.Int values that are not valid coordinates + CVE-2022-23772: math/big: prevent large memory consumption in Rat.SetString + CVE-2022-23773: cmd/go: prevent branches from materializing into versions All are minor security issues, so I'd like to go with stable-pu. [ Tests ] For CVE-2022-23806 and CVE-2022-23772, regression tests are backported as well. For CVE-2022-23773 the tests in upstream patch are hard to backport, so I test it manully. The test is similar with upstream patch[1] [1] https://github.com/golang/go/commit/fa4d9b8e2bc2612960c80474fca83a4c85a974eb#diff-6d41824e441b8846a74c31ab4968dc114a1e650c05172e1f89826ea9e55d4c5aR421 For example, running GOPROXY=direct /usr/lib/go-1.15/bin/go get vcs-test.golang.org/git/semver-branch.git@v1.0.0 Will get same result and error in [1]. [ Risks ] Patch for CVE-2022-23806 and CVE-2022-23772 are trivial and easy to review. Patch for CVE-2022-23773 is larger, and is backported by 3way merge. [ Checklist ] [x] *all* changes are documented in the d/changelog [x] I reviewed all changes and I approve them [x] attach debdiff against the package in (old)stable [ ] the issue is verified as fixed in unstable golang-1.15 has been removed from unstable. [ Changes ] See attachment. [ Other info ] CVE-2022-23806 and CVE-2022-23772 are for Go std library, which is statically linked in all Go programs. But these issues look like too minor to rebuild all Go programs.
diff -Nru golang-1.15-1.15.15/debian/changelog golang-1.15-1.15.15/debian/changelog --- golang-1.15-1.15.15/debian/changelog 2021-12-04 17:37:57.000000000 +0800 +++ golang-1.15-1.15.15/debian/changelog 2022-02-11 23:45:44.000000000 +0800 @@ -1,3 +1,14 @@ +golang-1.15 (1.15.15-1~deb11u3) bullseye; urgency=medium + + * Backport patches for CVE-2022-23806 CVE-2022-23772 CVE-2022-23773 + + CVE-2022-23806: crypto/elliptic: fix IsOnCurve for big.Int values + that are not valid coordinates + + CVE-2022-23772: math/big: prevent large memory consumption in + Rat.SetString + + CVE-2022-23773: cmd/go: prevent branches from materializing into versions + + -- Shengjing Zhu <z...@debian.org> Fri, 11 Feb 2022 23:45:44 +0800 + golang-1.15 (1.15.15-1~deb11u2) bullseye; urgency=medium * Backport patch for CVE-2021-38297 diff -Nru golang-1.15-1.15.15/debian/patches/0012-CVE-2022-23806.patch golang-1.15-1.15.15/debian/patches/0012-CVE-2022-23806.patch --- golang-1.15-1.15.15/debian/patches/0012-CVE-2022-23806.patch 1970-01-01 08:00:00.000000000 +0800 +++ golang-1.15-1.15.15/debian/patches/0012-CVE-2022-23806.patch 2022-02-11 23:45:44.000000000 +0800 @@ -0,0 +1,132 @@ +From: Filippo Valsorda <fili...@golang.org> +Date: Wed, 2 Feb 2022 09:15:44 -0800 +Subject: CVE-2022-23806 + +Origin: backport, https://github.com/golang/go/commit/6b3e741a +--- + src/crypto/elliptic/elliptic.go | 5 +++ + src/crypto/elliptic/elliptic_test.go | 81 ++++++++++++++++++++++++++++++++++++ + src/crypto/elliptic/p224.go | 5 +++ + 3 files changed, 91 insertions(+) + +diff --git a/src/crypto/elliptic/elliptic.go b/src/crypto/elliptic/elliptic.go +index f93dc16..afedf18 100644 +--- a/src/crypto/elliptic/elliptic.go ++++ b/src/crypto/elliptic/elliptic.go +@@ -71,6 +71,11 @@ func (curve *CurveParams) polynomial(x *big.Int) *big.Int { + } + + func (curve *CurveParams) 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 ++ } ++ + // 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 e80e773..bb16b0d 100644 +--- a/src/crypto/elliptic/elliptic_test.go ++++ b/src/crypto/elliptic/elliptic_test.go +@@ -721,3 +721,84 @@ func testMarshalCompressed(t *testing.T, curve Curve, x, y *big.Int, want []byte + t.Errorf("point did not round-trip correctly: got (%v, %v), want (%v, %v)", X, Y, x, y) + } + } ++ ++func testAllCurves(t *testing.T, f func(*testing.T, Curve)) { ++ tests := []struct { ++ name string ++ curve Curve ++ }{ ++ {"P256", P256()}, ++ {"P256/Params", P256().Params()}, ++ {"P224", P224()}, ++ {"P224/Params", P224().Params()}, ++ {"P384", P384()}, ++ {"P384/Params", P384().Params()}, ++ {"P521", P521()}, ++ {"P521/Params", P521().Params()}, ++ } ++ if testing.Short() { ++ tests = tests[:1] ++ } ++ for _, test := range tests { ++ curve := test.curve ++ t.Run(test.name, func(t *testing.T) { ++ t.Parallel() ++ f(t, 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) ++ } ++} +diff --git a/src/crypto/elliptic/p224.go b/src/crypto/elliptic/p224.go +index 8c76021..ff5c834 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 -Nru golang-1.15-1.15.15/debian/patches/0013-CVE-2022-23772.patch golang-1.15-1.15.15/debian/patches/0013-CVE-2022-23772.patch --- golang-1.15-1.15.15/debian/patches/0013-CVE-2022-23772.patch 1970-01-01 08:00:00.000000000 +0800 +++ golang-1.15-1.15.15/debian/patches/0013-CVE-2022-23772.patch 2022-02-11 23:45:44.000000000 +0800 @@ -0,0 +1,38 @@ +From: Katie Hockman <ka...@golang.org> +Date: Wed, 19 Jan 2022 16:54:41 -0500 +Subject: CVE-2022-23772 + +Origin: backport, https://github.com/golang/go/commit/07ee9e64 +--- + 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 ac3c8bd..90053a9 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 15d206c..e55e655 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}, diff -Nru golang-1.15-1.15.15/debian/patches/0014-CVE-2022-23773.patch golang-1.15-1.15.15/debian/patches/0014-CVE-2022-23773.patch --- golang-1.15-1.15.15/debian/patches/0014-CVE-2022-23773.patch 1970-01-01 08:00:00.000000000 +0800 +++ golang-1.15-1.15.15/debian/patches/0014-CVE-2022-23773.patch 2022-02-11 23:45:44.000000000 +0800 @@ -0,0 +1,323 @@ +From: "Bryan C. Mills" <bcmi...@google.com> +Date: Thu, 13 Jan 2022 15:38:14 -0500 +Subject: CVE-2022-23773 + +Origin: backport, https://github.com/golang/go/commit/de76489a + +Only change in coderep.go is backported. Changes in coderepo_test.go +and mod_invalid_version.txt are used for testing, which can't be +cherry-picked directly. +--- + src/cmd/go/internal/modfetch/coderepo.go | 215 +++++++++++++++---------------- + 1 file changed, 106 insertions(+), 109 deletions(-) + +diff --git a/src/cmd/go/internal/modfetch/coderepo.go b/src/cmd/go/internal/modfetch/coderepo.go +index d043903..3297af9 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))) + } + } +- +- 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)) ++ } else if strings.HasSuffix(v, "+incompatible") { ++ errIncompatible = invalidf("+incompatible suffix not allowed: major version %s is compatible", semver.Major(v)) ++ } ++ ++ 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 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 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. +@@ -439,65 +447,59 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e + 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 { ++ 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) + } + + if pseudoBase == "" { +@@ -511,11 +513,10 @@ func (r *codeRepo) convert(info *codehost.RevInfo, statVers string) (*RevInfo, e + tag, _ = r.code.RecentTag(info.Name, tagPrefix, "v0") + } + } +- pseudoBase, _ = tagToVersion(tag) // empty if the tag is invalid ++ pseudoBase, _ = tagToVersion(tag) + } + +- info2.Version = PseudoVersion(r.pseudoMajor, pseudoBase, info.Time, info.Short) +- return checkGoMod() ++ return checkCanonical(PseudoVersion(r.pseudoMajor, pseudoBase, info.Time, info.Short)) + } + + // validatePseudoVersion checks that version has a major version compatible with +@@ -539,10 +540,6 @@ func (r *codeRepo) validatePseudoVersion(info *codehost.RevInfo, version string) + } + }() + +- if err := module.CheckPathMajor(version, r.pathMajor); err != nil { +- return err +- } +- + rev, err := PseudoVersionRev(version) + if err != nil { + return err diff -Nru golang-1.15-1.15.15/debian/patches/series golang-1.15-1.15.15/debian/patches/series --- golang-1.15-1.15.15/debian/patches/series 2021-12-04 17:37:57.000000000 +0800 +++ golang-1.15-1.15.15/debian/patches/series 2022-02-11 23:45:44.000000000 +0800 @@ -9,3 +9,6 @@ 0009-CVE-2021-41771.patch 0010-CVE-2021-44716.patch 0011-CVE-2021-44717.patch +0012-CVE-2022-23806.patch +0013-CVE-2022-23772.patch +0014-CVE-2022-23773.patch