From patchwork Wed Aug 2 00:18:11 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sakib Sajal X-Patchwork-Id: 28291 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4F332EB64DD for ; Wed, 2 Aug 2023 00:18:35 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.web10.3938.1690935504478884611 for ; Tue, 01 Aug 2023 17:18:26 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=cVQC6vkJ; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=757808ff50=sakib.sajal@windriver.com) Received: from pps.filterd (m0250809.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.17.1.22/8.17.1.22) with ESMTP id 371NoxVE017988 for ; Tue, 1 Aug 2023 17:18:22 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=from:to:subject:date:message-id:mime-version :content-transfer-encoding:content-type; s=PPS06212021; bh=QDQoe /MF1JwVQ0ZFeFWpXl+1IOdl7XUZH9NKwp1K5vg=; b=cVQC6vkJeC/lKSkJMX3rT lEDTeJgeJNMovHiDWFAQoszr0d4vBZZc8KXgVvEtahzt/7L491rZHKgPijWcZ552 njBm1mXHEIrKTmdif/4rLFn5oquHJ0wwiGhnZqNUWntmVUX558ZcOh6SHTw380yi SZ9/Kt9kHU3bPR3f1rRBdr1LLXpftDyVQDhL3hzK5y2jdBzKPHWeTog39+mc9FMd hH8nlE0UbsKq65yuHsq7uhaYkOFMbZfRkUym1QgynWOAo7SlpkcJHi2YeK7xlzjz 6pJ4b9OZuNn+iNtLXFFzbYGYRn+Q9zcUYmGSKC6Yem0NF0+BUeBD4Gq/JQJwrCFY w== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [147.11.82.252]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 3s78py8532-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Tue, 01 Aug 2023 17:18:21 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (147.11.82.252) by ala-exchng01.corp.ad.wrs.com (147.11.82.252) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.27; Tue, 1 Aug 2023 17:18:21 -0700 Received: from ala-lpggp3.wrs.com (147.11.105.124) by ala-exchng01.corp.ad.wrs.com (147.11.82.252) with Microsoft SMTP Server id 15.1.2507.27 via Frontend Transport; Tue, 1 Aug 2023 17:18:21 -0700 From: Sakib Sajal To: Subject: [kirkstone][PATCH 1/3] go: fix CVE-2023-24536 Date: Tue, 1 Aug 2023 17:18:11 -0700 Message-ID: <20230802001813.3521965-1-sakib.sajal@windriver.com> X-Mailer: git-send-email 2.39.0 MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: 1azOBL5pTJCbRSOUfYuiOBfcIEj59SM9 X-Proofpoint-GUID: 1azOBL5pTJCbRSOUfYuiOBfcIEj59SM9 X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.957,Hydra:6.0.591,FMLib:17.11.176.26 definitions=2023-08-01_21,2023-08-01_01,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 malwarescore=0 mlxlogscore=999 priorityscore=1501 bulkscore=0 lowpriorityscore=0 adultscore=0 suspectscore=0 mlxscore=0 phishscore=0 spamscore=0 impostorscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2306200000 definitions=main-2308020000 List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 02 Aug 2023 00:18:35 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/185366 Backport required patches to fix CVE-2023-24536. Signed-off-by: Sakib Sajal --- meta/recipes-devtools/go/go-1.17.13.inc | 3 + .../go/go-1.19/CVE-2023-24536_1.patch | 137 +++++++ .../go/go-1.19/CVE-2023-24536_2.patch | 187 ++++++++++ .../go/go-1.19/CVE-2023-24536_3.patch | 349 ++++++++++++++++++ 4 files changed, 676 insertions(+) create mode 100644 meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch create mode 100644 meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch create mode 100644 meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch diff --git a/meta/recipes-devtools/go/go-1.17.13.inc b/meta/recipes-devtools/go/go-1.17.13.inc index 73921852fc..f8b046500a 100644 --- a/meta/recipes-devtools/go/go-1.17.13.inc +++ b/meta/recipes-devtools/go/go-1.17.13.inc @@ -36,6 +36,9 @@ SRC_URI += "\ file://CVE-2023-29405.patch \ file://CVE-2023-29402.patch \ file://CVE-2023-29400.patch \ + file://CVE-2023-24536_1.patch \ + file://CVE-2023-24536_2.patch \ + file://CVE-2023-24536_3.patch \ " SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" diff --git a/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch new file mode 100644 index 0000000000..ff9ba18ec5 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_1.patch @@ -0,0 +1,137 @@ +From f8d691d335c6ac14bcbae6886b5bf8ca8bf1e6a5 Mon Sep 17 00:00:00 2001 +From: Damien Neil +Date: Thu, 16 Mar 2023 14:18:04 -0700 +Subject: [PATCH 1/3] mime/multipart: avoid excessive copy buffer allocations + in ReadForm + +When copying form data to disk with io.Copy, +allocate only one copy buffer and reuse it rather than +creating two buffers per file (one from io.multiReader.WriteTo, +and a second one from os.File.ReadFrom). + +Thanks to Jakob Ackermann (@das7pad) for reporting this issue. + +For CVE-2023-24536 +For #59153 +For #59269 + +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802453 +Run-TryBot: Damien Neil +Reviewed-by: Julie Qiu +Reviewed-by: Roland Shoemaker +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802395 +Run-TryBot: Roland Shoemaker +Reviewed-by: Damien Neil +Change-Id: Ie405470c92abffed3356913b37d813e982c96c8b +Reviewed-on: https://go-review.googlesource.com/c/go/+/481983 +Run-TryBot: Michael Knyszek +TryBot-Result: Gopher Robot +Auto-Submit: Michael Knyszek +Reviewed-by: Matthew Dempsky + +CVE: CVE-2023-24536 +Upstream-Status: Backport [ef41a4e2face45e580c5836eaebd51629fc23f15] +Signed-off-by: Sakib Sajal +--- + src/mime/multipart/formdata.go | 15 +++++++-- + src/mime/multipart/formdata_test.go | 49 +++++++++++++++++++++++++++++ + 2 files changed, 61 insertions(+), 3 deletions(-) + +diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go +index a7d4ca9..975dcb6 100644 +--- a/src/mime/multipart/formdata.go ++++ b/src/mime/multipart/formdata.go +@@ -84,6 +84,7 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { + maxMemoryBytes = math.MaxInt64 + } + } ++ var copyBuf []byte + for { + p, err := r.nextPart(false, maxMemoryBytes) + if err == io.EOF { +@@ -147,14 +148,22 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { + } + } + numDiskFiles++ +- size, err := io.Copy(file, io.MultiReader(&b, p)) ++ if _, err := file.Write(b.Bytes()); err != nil { ++ return nil, err ++ } ++ if copyBuf == nil { ++ copyBuf = make([]byte, 32*1024) // same buffer size as io.Copy uses ++ } ++ // os.File.ReadFrom will allocate its own copy buffer if we let io.Copy use it. ++ type writerOnly struct{ io.Writer } ++ remainingSize, err := io.CopyBuffer(writerOnly{file}, p, copyBuf) + if err != nil { + return nil, err + } + fh.tmpfile = file.Name() +- fh.Size = size ++ fh.Size = int64(b.Len()) + remainingSize + fh.tmpoff = fileOff +- fileOff += size ++ fileOff += fh.Size + if !combineFiles { + if err := file.Close(); err != nil { + return nil, err +diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go +index 5cded71..f5b5608 100644 +--- a/src/mime/multipart/formdata_test.go ++++ b/src/mime/multipart/formdata_test.go +@@ -368,3 +368,52 @@ func testReadFormManyFiles(t *testing.T, distinct bool) { + t.Fatalf("temp dir contains %v files; want 0", len(names)) + } + } ++ ++func BenchmarkReadForm(b *testing.B) { ++ for _, test := range []struct { ++ name string ++ form func(fw *Writer, count int) ++ }{{ ++ name: "fields", ++ form: func(fw *Writer, count int) { ++ for i := 0; i < count; i++ { ++ w, _ := fw.CreateFormField(fmt.Sprintf("field%v", i)) ++ fmt.Fprintf(w, "value %v", i) ++ } ++ }, ++ }, { ++ name: "files", ++ form: func(fw *Writer, count int) { ++ for i := 0; i < count; i++ { ++ w, _ := fw.CreateFormFile(fmt.Sprintf("field%v", i), fmt.Sprintf("file%v", i)) ++ fmt.Fprintf(w, "value %v", i) ++ } ++ }, ++ }} { ++ b.Run(test.name, func(b *testing.B) { ++ for _, maxMemory := range []int64{ ++ 0, ++ 1 << 20, ++ } { ++ var buf bytes.Buffer ++ fw := NewWriter(&buf) ++ test.form(fw, 10) ++ if err := fw.Close(); err != nil { ++ b.Fatal(err) ++ } ++ b.Run(fmt.Sprintf("maxMemory=%v", maxMemory), func(b *testing.B) { ++ b.ReportAllocs() ++ for i := 0; i < b.N; i++ { ++ fr := NewReader(bytes.NewReader(buf.Bytes()), fw.Boundary()) ++ form, err := fr.ReadForm(maxMemory) ++ if err != nil { ++ b.Fatal(err) ++ } ++ form.RemoveAll() ++ } ++ ++ }) ++ } ++ }) ++ } ++} +-- +2.35.5 + diff --git a/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch new file mode 100644 index 0000000000..704a1fb567 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_2.patch @@ -0,0 +1,187 @@ +From 4174a87b600c58e8cc00d9d18d0c507c67ca5d41 Mon Sep 17 00:00:00 2001 +From: Damien Neil +Date: Thu, 16 Mar 2023 16:56:12 -0700 +Subject: [PATCH 2/3] net/textproto, mime/multipart: improve accounting of + non-file data + +For requests containing large numbers of small parts, +memory consumption of a parsed form could be about 250% +over the estimated size. + +When considering the size of parsed forms, account for the size of +FileHeader structs and increase the estimate of memory consumed by +map entries. + +Thanks to Jakob Ackermann (@das7pad) for reporting this issue. + +For CVE-2023-24536 +For #59153 +For #59269 + +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802454 +Run-TryBot: Damien Neil +Reviewed-by: Roland Shoemaker +Reviewed-by: Julie Qiu +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802396 +Run-TryBot: Roland Shoemaker +Reviewed-by: Damien Neil +Change-Id: I31bc50e9346b4eee6fbe51a18c3c57230cc066db +Reviewed-on: https://go-review.googlesource.com/c/go/+/481984 +Reviewed-by: Matthew Dempsky +Auto-Submit: Michael Knyszek +TryBot-Result: Gopher Robot +Run-TryBot: Michael Knyszek + +CVE: CVE-2023-24536 +Upstream-Status: Backport [7a359a651c7ebdb29e0a1c03102fce793e9f58f0] +Signed-off-by: Sakib Sajal +--- + src/mime/multipart/formdata.go | 9 +++-- + src/mime/multipart/formdata_test.go | 55 ++++++++++++----------------- + src/net/textproto/reader.go | 8 ++++- + 3 files changed, 37 insertions(+), 35 deletions(-) + +diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go +index 975dcb6..3f6ff69 100644 +--- a/src/mime/multipart/formdata.go ++++ b/src/mime/multipart/formdata.go +@@ -103,8 +103,9 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { + // Multiple values for the same key (one map entry, longer slice) are cheaper + // than the same number of values for different keys (many map entries), but + // using a consistent per-value cost for overhead is simpler. ++ const mapEntryOverhead = 200 + maxMemoryBytes -= int64(len(name)) +- maxMemoryBytes -= 100 // map overhead ++ maxMemoryBytes -= mapEntryOverhead + if maxMemoryBytes < 0 { + // We can't actually take this path, since nextPart would already have + // rejected the MIME headers for being too large. Check anyway. +@@ -128,7 +129,10 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { + } + + // file, store in memory or on disk ++ const fileHeaderSize = 100 + maxMemoryBytes -= mimeHeaderSize(p.Header) ++ maxMemoryBytes -= mapEntryOverhead ++ maxMemoryBytes -= fileHeaderSize + if maxMemoryBytes < 0 { + return nil, ErrMessageTooLarge + } +@@ -183,9 +187,10 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { + } + + func mimeHeaderSize(h textproto.MIMEHeader) (size int64) { ++ size = 400 + for k, vs := range h { + size += int64(len(k)) +- size += 100 // map entry overhead ++ size += 200 // map entry overhead + for _, v := range vs { + size += int64(len(v)) + } +diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go +index f5b5608..8ed26e0 100644 +--- a/src/mime/multipart/formdata_test.go ++++ b/src/mime/multipart/formdata_test.go +@@ -192,10 +192,10 @@ func (r *failOnReadAfterErrorReader) Read(p []byte) (n int, err error) { + // TestReadForm_NonFileMaxMemory asserts that the ReadForm maxMemory limit is applied + // while processing non-file form data as well as file form data. + func TestReadForm_NonFileMaxMemory(t *testing.T) { +- n := 10<<20 + 25 + if testing.Short() { +- n = 10<<10 + 25 ++ t.Skip("skipping in -short mode") + } ++ n := 10 << 20 + largeTextValue := strings.Repeat("1", n) + message := `--MyBoundary + Content-Disposition: form-data; name="largetext" +@@ -203,38 +203,29 @@ Content-Disposition: form-data; name="largetext" + ` + largeTextValue + ` + --MyBoundary-- + ` +- + testBody := strings.ReplaceAll(message, "\n", "\r\n") +- testCases := []struct { +- name string +- maxMemory int64 +- err error +- }{ +- {"smaller", 50 + int64(len("largetext")) + 100, nil}, +- {"exact-fit", 25 + int64(len("largetext")) + 100, nil}, +- {"too-large", 0, ErrMessageTooLarge}, +- } +- for _, tc := range testCases { +- t.Run(tc.name, func(t *testing.T) { +- if tc.maxMemory == 0 && testing.Short() { +- t.Skip("skipping in -short mode") +- } +- b := strings.NewReader(testBody) +- r := NewReader(b, boundary) +- f, err := r.ReadForm(tc.maxMemory) +- if err == nil { +- defer f.RemoveAll() +- } +- if tc.err != err { +- t.Fatalf("ReadForm error - got: %v; expected: %v", err, tc.err) +- } +- if err == nil { +- if g := f.Value["largetext"][0]; g != largeTextValue { +- t.Errorf("largetext mismatch: got size: %v, expected size: %v", len(g), len(largeTextValue)) +- } +- } +- }) ++ // Try parsing the form with increasing maxMemory values. ++ // Changes in how we account for non-file form data may cause the exact point ++ // where we change from rejecting the form as too large to accepting it to vary, ++ // but we should see both successes and failures. ++ const failWhenMaxMemoryLessThan = 128 ++ for maxMemory := int64(0); maxMemory < failWhenMaxMemoryLessThan*2; maxMemory += 16 { ++ b := strings.NewReader(testBody) ++ r := NewReader(b, boundary) ++ f, err := r.ReadForm(maxMemory) ++ if err != nil { ++ continue ++ } ++ if g := f.Value["largetext"][0]; g != largeTextValue { ++ t.Errorf("largetext mismatch: got size: %v, expected size: %v", len(g), len(largeTextValue)) ++ } ++ f.RemoveAll() ++ if maxMemory < failWhenMaxMemoryLessThan { ++ t.Errorf("ReadForm(%v): no error, expect to hit memory limit when maxMemory < %v", maxMemory, failWhenMaxMemoryLessThan) ++ } ++ return + } ++ t.Errorf("ReadForm(x) failed for x < 1024, expect success") + } + + // TestReadForm_MetadataTooLarge verifies that we account for the size of field names, +diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go +index fcbede8..9af4c49 100644 +--- a/src/net/textproto/reader.go ++++ b/src/net/textproto/reader.go +@@ -503,6 +503,12 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { + + m := make(MIMEHeader, hint) + ++ // Account for 400 bytes of overhead for the MIMEHeader, plus 200 bytes per entry. ++ // Benchmarking map creation as of go1.20, a one-entry MIMEHeader is 416 bytes and large ++ // MIMEHeaders average about 200 bytes per entry. ++ lim -= 400 ++ const mapEntryOverhead = 200 ++ + // The first line cannot start with a leading space. + if buf, err := r.R.Peek(1); err == nil && (buf[0] == ' ' || buf[0] == '\t') { + line, err := r.readLineSlice() +@@ -552,7 +558,7 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { + vv := m[key] + if vv == nil { + lim -= int64(len(key)) +- lim -= 100 // map entry overhead ++ lim -= mapEntryOverhead + } + lim -= int64(len(value)) + if lim < 0 { +-- +2.35.5 + diff --git a/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch new file mode 100644 index 0000000000..6de04e9a61 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.19/CVE-2023-24536_3.patch @@ -0,0 +1,349 @@ +From ec763bc936f76cec0fe71a791c6bb7d4ac5f3e46 Mon Sep 17 00:00:00 2001 +From: Damien Neil +Date: Mon, 20 Mar 2023 10:43:19 -0700 +Subject: [PATCH 3/3] mime/multipart: limit parsed mime message sizes + +The parsed forms of MIME headers and multipart forms can consume +substantially more memory than the size of the input data. +A malicious input containing a very large number of headers or +form parts can cause excessively large memory allocations. + +Set limits on the size of MIME data: + +Reader.NextPart and Reader.NextRawPart limit the the number +of headers in a part to 10000. + +Reader.ReadForm limits the total number of headers in all +FileHeaders to 10000. + +Both of these limits may be set with with +GODEBUG=multipartmaxheaders=. + +Reader.ReadForm limits the number of parts in a form to 1000. +This limit may be set with GODEBUG=multipartmaxparts=. + +Thanks for Jakob Ackermann (@das7pad) for reporting this issue. + +For CVE-2023-24536 +For #59153 +For #59269 + +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1802455 +Run-TryBot: Damien Neil +Reviewed-by: Roland Shoemaker +Reviewed-by: Julie Qiu +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1801087 +Reviewed-by: Damien Neil +Run-TryBot: Roland Shoemaker +Change-Id: If134890d75f0d95c681d67234daf191ba08e6424 +Reviewed-on: https://go-review.googlesource.com/c/go/+/481985 +Run-TryBot: Michael Knyszek +Auto-Submit: Michael Knyszek +TryBot-Result: Gopher Robot +Reviewed-by: Matthew Dempsky + +CVE: CVE-2023-24536 +Upstream-Status: Backport [7917b5f31204528ea72e0629f0b7d52b35b27538] +Signed-off-by: Sakib Sajal +--- + src/mime/multipart/formdata.go | 19 ++++++++- + src/mime/multipart/formdata_test.go | 61 ++++++++++++++++++++++++++++ + src/mime/multipart/multipart.go | 31 ++++++++++---- + src/mime/multipart/readmimeheader.go | 2 +- + src/net/textproto/reader.go | 19 +++++---- + 5 files changed, 115 insertions(+), 17 deletions(-) + +diff --git a/src/mime/multipart/formdata.go b/src/mime/multipart/formdata.go +index 3f6ff69..4f26aab 100644 +--- a/src/mime/multipart/formdata.go ++++ b/src/mime/multipart/formdata.go +@@ -12,6 +12,7 @@ import ( + "math" + "net/textproto" + "os" ++ "strconv" + ) + + // ErrMessageTooLarge is returned by ReadForm if the message form +@@ -41,6 +42,15 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { + numDiskFiles := 0 + multipartFiles := godebug.Get("multipartfiles") + combineFiles := multipartFiles != "distinct" ++ maxParts := 1000 ++ multipartMaxParts := godebug.Get("multipartmaxparts") ++ if multipartMaxParts != "" { ++ if v, err := strconv.Atoi(multipartMaxParts); err == nil && v >= 0 { ++ maxParts = v ++ } ++ } ++ maxHeaders := maxMIMEHeaders() ++ + defer func() { + if file != nil { + if cerr := file.Close(); err == nil { +@@ -86,13 +96,17 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { + } + var copyBuf []byte + for { +- p, err := r.nextPart(false, maxMemoryBytes) ++ p, err := r.nextPart(false, maxMemoryBytes, maxHeaders) + if err == io.EOF { + break + } + if err != nil { + return nil, err + } ++ if maxParts <= 0 { ++ return nil, ErrMessageTooLarge ++ } ++ maxParts-- + + name := p.FormName() + if name == "" { +@@ -136,6 +150,9 @@ func (r *Reader) readForm(maxMemory int64) (_ *Form, err error) { + if maxMemoryBytes < 0 { + return nil, ErrMessageTooLarge + } ++ for _, v := range p.Header { ++ maxHeaders -= int64(len(v)) ++ } + fh := &FileHeader{ + Filename: filename, + Header: p.Header, +diff --git a/src/mime/multipart/formdata_test.go b/src/mime/multipart/formdata_test.go +index 8ed26e0..c78eeb7 100644 +--- a/src/mime/multipart/formdata_test.go ++++ b/src/mime/multipart/formdata_test.go +@@ -360,6 +360,67 @@ func testReadFormManyFiles(t *testing.T, distinct bool) { + } + } + ++func TestReadFormLimits(t *testing.T) { ++ for _, test := range []struct { ++ values int ++ files int ++ extraKeysPerFile int ++ wantErr error ++ godebug string ++ }{ ++ {values: 1000}, ++ {values: 1001, wantErr: ErrMessageTooLarge}, ++ {values: 500, files: 500}, ++ {values: 501, files: 500, wantErr: ErrMessageTooLarge}, ++ {files: 1000}, ++ {files: 1001, wantErr: ErrMessageTooLarge}, ++ {files: 1, extraKeysPerFile: 9998}, // plus Content-Disposition and Content-Type ++ {files: 1, extraKeysPerFile: 10000, wantErr: ErrMessageTooLarge}, ++ {godebug: "multipartmaxparts=100", values: 100}, ++ {godebug: "multipartmaxparts=100", values: 101, wantErr: ErrMessageTooLarge}, ++ {godebug: "multipartmaxheaders=100", files: 2, extraKeysPerFile: 48}, ++ {godebug: "multipartmaxheaders=100", files: 2, extraKeysPerFile: 50, wantErr: ErrMessageTooLarge}, ++ } { ++ name := fmt.Sprintf("values=%v/files=%v/extraKeysPerFile=%v", test.values, test.files, test.extraKeysPerFile) ++ if test.godebug != "" { ++ name += fmt.Sprintf("/godebug=%v", test.godebug) ++ } ++ t.Run(name, func(t *testing.T) { ++ if test.godebug != "" { ++ t.Setenv("GODEBUG", test.godebug) ++ } ++ var buf bytes.Buffer ++ fw := NewWriter(&buf) ++ for i := 0; i < test.values; i++ { ++ w, _ := fw.CreateFormField(fmt.Sprintf("field%v", i)) ++ fmt.Fprintf(w, "value %v", i) ++ } ++ for i := 0; i < test.files; i++ { ++ h := make(textproto.MIMEHeader) ++ h.Set("Content-Disposition", ++ fmt.Sprintf(`form-data; name="file%v"; filename="file%v"`, i, i)) ++ h.Set("Content-Type", "application/octet-stream") ++ for j := 0; j < test.extraKeysPerFile; j++ { ++ h.Set(fmt.Sprintf("k%v", j), "v") ++ } ++ w, _ := fw.CreatePart(h) ++ fmt.Fprintf(w, "value %v", i) ++ } ++ if err := fw.Close(); err != nil { ++ t.Fatal(err) ++ } ++ fr := NewReader(bytes.NewReader(buf.Bytes()), fw.Boundary()) ++ form, err := fr.ReadForm(1 << 10) ++ if err == nil { ++ defer form.RemoveAll() ++ } ++ if err != test.wantErr { ++ t.Errorf("ReadForm = %v, want %v", err, test.wantErr) ++ } ++ }) ++ } ++} ++ + func BenchmarkReadForm(b *testing.B) { + for _, test := range []struct { + name string +diff --git a/src/mime/multipart/multipart.go b/src/mime/multipart/multipart.go +index 19fe0ea..80acabc 100644 +--- a/src/mime/multipart/multipart.go ++++ b/src/mime/multipart/multipart.go +@@ -16,11 +16,13 @@ import ( + "bufio" + "bytes" + "fmt" ++ "internal/godebug" + "io" + "mime" + "mime/quotedprintable" + "net/textproto" + "path/filepath" ++ "strconv" + "strings" + ) + +@@ -128,12 +130,12 @@ func (r *stickyErrorReader) Read(p []byte) (n int, _ error) { + return n, r.err + } + +-func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize int64) (*Part, error) { ++func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize, maxMIMEHeaders int64) (*Part, error) { + bp := &Part{ + Header: make(map[string][]string), + mr: mr, + } +- if err := bp.populateHeaders(maxMIMEHeaderSize); err != nil { ++ if err := bp.populateHeaders(maxMIMEHeaderSize, maxMIMEHeaders); err != nil { + return nil, err + } + bp.r = partReader{bp} +@@ -149,9 +151,9 @@ func newPart(mr *Reader, rawPart bool, maxMIMEHeaderSize int64) (*Part, error) { + return bp, nil + } + +-func (bp *Part) populateHeaders(maxMIMEHeaderSize int64) error { ++func (bp *Part) populateHeaders(maxMIMEHeaderSize, maxMIMEHeaders int64) error { + r := textproto.NewReader(bp.mr.bufReader) +- header, err := readMIMEHeader(r, maxMIMEHeaderSize) ++ header, err := readMIMEHeader(r, maxMIMEHeaderSize, maxMIMEHeaders) + if err == nil { + bp.Header = header + } +@@ -313,6 +315,19 @@ type Reader struct { + // including header keys, values, and map overhead. + const maxMIMEHeaderSize = 10 << 20 + ++func maxMIMEHeaders() int64 { ++ // multipartMaxHeaders is the maximum number of header entries NextPart will return, ++ // as well as the maximum combined total of header entries Reader.ReadForm will return ++ // in FileHeaders. ++ multipartMaxHeaders := godebug.Get("multipartmaxheaders") ++ if multipartMaxHeaders != "" { ++ if v, err := strconv.ParseInt(multipartMaxHeaders, 10, 64); err == nil && v >= 0 { ++ return v ++ } ++ } ++ return 10000 ++} ++ + // NextPart returns the next part in the multipart or an error. + // When there are no more parts, the error io.EOF is returned. + // +@@ -320,7 +335,7 @@ const maxMIMEHeaderSize = 10 << 20 + // has a value of "quoted-printable", that header is instead + // hidden and the body is transparently decoded during Read calls. + func (r *Reader) NextPart() (*Part, error) { +- return r.nextPart(false, maxMIMEHeaderSize) ++ return r.nextPart(false, maxMIMEHeaderSize, maxMIMEHeaders()) + } + + // NextRawPart returns the next part in the multipart or an error. +@@ -329,10 +344,10 @@ func (r *Reader) NextPart() (*Part, error) { + // Unlike NextPart, it does not have special handling for + // "Content-Transfer-Encoding: quoted-printable". + func (r *Reader) NextRawPart() (*Part, error) { +- return r.nextPart(true, maxMIMEHeaderSize) ++ return r.nextPart(true, maxMIMEHeaderSize, maxMIMEHeaders()) + } + +-func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize int64) (*Part, error) { ++func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize, maxMIMEHeaders int64) (*Part, error) { + if r.currentPart != nil { + r.currentPart.Close() + } +@@ -357,7 +372,7 @@ func (r *Reader) nextPart(rawPart bool, maxMIMEHeaderSize int64) (*Part, error) + + if r.isBoundaryDelimiterLine(line) { + r.partsRead++ +- bp, err := newPart(r, rawPart, maxMIMEHeaderSize) ++ bp, err := newPart(r, rawPart, maxMIMEHeaderSize, maxMIMEHeaders) + if err != nil { + return nil, err + } +diff --git a/src/mime/multipart/readmimeheader.go b/src/mime/multipart/readmimeheader.go +index 6836928..25aa6e2 100644 +--- a/src/mime/multipart/readmimeheader.go ++++ b/src/mime/multipart/readmimeheader.go +@@ -11,4 +11,4 @@ import ( + // readMIMEHeader is defined in package net/textproto. + // + //go:linkname readMIMEHeader net/textproto.readMIMEHeader +-func readMIMEHeader(r *textproto.Reader, lim int64) (textproto.MIMEHeader, error) ++func readMIMEHeader(r *textproto.Reader, maxMemory, maxHeaders int64) (textproto.MIMEHeader, error) +diff --git a/src/net/textproto/reader.go b/src/net/textproto/reader.go +index 9af4c49..c6569c8 100644 +--- a/src/net/textproto/reader.go ++++ b/src/net/textproto/reader.go +@@ -483,12 +483,12 @@ func (r *Reader) ReadDotLines() ([]string, error) { + // } + // + func (r *Reader) ReadMIMEHeader() (MIMEHeader, error) { +- return readMIMEHeader(r, math.MaxInt64) ++ return readMIMEHeader(r, math.MaxInt64, math.MaxInt64) + } + + // readMIMEHeader is a version of ReadMIMEHeader which takes a limit on the header size. + // It is called by the mime/multipart package. +-func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { ++func readMIMEHeader(r *Reader, maxMemory, maxHeaders int64) (MIMEHeader, error) { + // Avoid lots of small slice allocations later by allocating one + // large one ahead of time which we'll cut up into smaller + // slices. If this isn't big enough later, we allocate small ones. +@@ -506,7 +506,7 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { + // Account for 400 bytes of overhead for the MIMEHeader, plus 200 bytes per entry. + // Benchmarking map creation as of go1.20, a one-entry MIMEHeader is 416 bytes and large + // MIMEHeaders average about 200 bytes per entry. +- lim -= 400 ++ maxMemory -= 400 + const mapEntryOverhead = 200 + + // The first line cannot start with a leading space. +@@ -538,6 +538,11 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { + continue + } + ++ maxHeaders-- ++ if maxHeaders < 0 { ++ return nil, errors.New("message too large") ++ } ++ + // backport 5c55ac9bf1e5f779220294c843526536605f42ab + // + // value is computed as +@@ -557,11 +562,11 @@ func readMIMEHeader(r *Reader, lim int64) (MIMEHeader, error) { + + vv := m[key] + if vv == nil { +- lim -= int64(len(key)) +- lim -= mapEntryOverhead ++ maxMemory -= int64(len(key)) ++ maxMemory -= mapEntryOverhead + } +- lim -= int64(len(value)) +- if lim < 0 { ++ maxMemory -= int64(len(value)) ++ if maxMemory < 0 { + // TODO: This should be a distinguishable error (ErrMessageTooLarge) + // to allow mime/multipart to detect it. + return m, errors.New("message too large") +-- +2.35.5 + From patchwork Wed Aug 2 00:18:12 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sakib Sajal X-Patchwork-Id: 28290 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 526CDC00528 for ; Wed, 2 Aug 2023 00:18:35 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.web10.3937.1690935503993218265 for ; Tue, 01 Aug 2023 17:18:26 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@windriver.com header.s=PPS06212021 header.b=k8hNU6rK; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=757808ff50=sakib.sajal@windriver.com) Received: from pps.filterd (m0250809.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.17.1.22/8.17.1.22) with ESMTP id 371NoxVF017988 for ; Tue, 1 Aug 2023 17:18:22 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=from:to:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding:content-type; s= PPS06212021; bh=5IyXhK8TEKtEqkChcBLiZzG/IOlEvW9oXTz0OOzRqcE=; b= k8hNU6rKYrf4INQjtEz2mlPQM2e1IkIUjxRAY75lgeyozjNbZZqaOwnWe1XVvd0X ad+N47qBCPacE5DBFvjUasv7Ndh7wTgZO94g7XXWa1j+McrM/jebRSukxoh5IU8L LTuH91f0Re4yexgrdQdg7Mq7ilYeHLPDRVUj5PgDiTpsgHvKxt9O7ImIX6H2BcLs zqfjbMrNvGTOwIyN8NkR1rrU8KYnkgA31yeWo+pvdxn9vIzws98kiZp9/4q+k9sw 7OvPlOCViCqEWoAv2UjLM5hLzPAET9S0ZQyzGUtBGFEuwwRhocrzERKf1W7IcCeS uqDsBq197SUEA0ufL+ZRng== Received: from ala-exchng01.corp.ad.wrs.com (ala-exchng01.wrs.com [147.11.82.252]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 3s78py8532-2 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Tue, 01 Aug 2023 17:18:22 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (147.11.82.252) by ala-exchng01.corp.ad.wrs.com (147.11.82.252) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.27; Tue, 1 Aug 2023 17:18:21 -0700 Received: from ala-lpggp3.wrs.com (147.11.105.124) by ala-exchng01.corp.ad.wrs.com (147.11.82.252) with Microsoft SMTP Server id 15.1.2507.27 via Frontend Transport; Tue, 1 Aug 2023 17:18:21 -0700 From: Sakib Sajal To: Subject: [kirkstone][PATCH 2/3] go: fix CVE-2023-24531 Date: Tue, 1 Aug 2023 17:18:12 -0700 Message-ID: <20230802001813.3521965-2-sakib.sajal@windriver.com> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230802001813.3521965-1-sakib.sajal@windriver.com> References: <20230802001813.3521965-1-sakib.sajal@windriver.com> MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: tkHqTH7FYmccXHkwVf-IWNA6WZ5lVHAD X-Proofpoint-GUID: tkHqTH7FYmccXHkwVf-IWNA6WZ5lVHAD X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.957,Hydra:6.0.591,FMLib:17.11.176.26 definitions=2023-08-01_21,2023-08-01_01,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 clxscore=1015 malwarescore=0 mlxlogscore=999 priorityscore=1501 bulkscore=0 lowpriorityscore=0 adultscore=0 suspectscore=0 mlxscore=0 phishscore=0 spamscore=0 impostorscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2306200000 definitions=main-2308020000 List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 02 Aug 2023 00:18:35 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/185365 Backport required patches from go1.21 to fix CVE-2023-24531. Signed-off-by: Sakib Sajal --- meta/recipes-devtools/go/go-1.17.13.inc | 4 +- .../go/go-1.21/CVE-2023-24531_1.patch | 252 ++++++++++++++++++ .../go/go-1.21/CVE-2023-24531_2.patch | 47 ++++ 3 files changed, 302 insertions(+), 1 deletion(-) create mode 100644 meta/recipes-devtools/go/go-1.21/CVE-2023-24531_1.patch create mode 100644 meta/recipes-devtools/go/go-1.21/CVE-2023-24531_2.patch diff --git a/meta/recipes-devtools/go/go-1.17.13.inc b/meta/recipes-devtools/go/go-1.17.13.inc index f8b046500a..d46eab01a0 100644 --- a/meta/recipes-devtools/go/go-1.17.13.inc +++ b/meta/recipes-devtools/go/go-1.17.13.inc @@ -1,6 +1,6 @@ require go-common.inc -FILESEXTRAPATHS:prepend := "${FILE_DIRNAME}/go-1.19:${FILE_DIRNAME}/go-1.18:" +FILESEXTRAPATHS:prepend := "${FILE_DIRNAME}/go-1.21:${FILE_DIRNAME}/go-1.19:${FILE_DIRNAME}/go-1.18:" LIC_FILES_CHKSUM = "file://LICENSE;md5=5d4950ecb7b26d2c5e4e7b4e0dd74707" @@ -39,6 +39,8 @@ SRC_URI += "\ file://CVE-2023-24536_1.patch \ file://CVE-2023-24536_2.patch \ file://CVE-2023-24536_3.patch \ + file://CVE-2023-24531_1.patch \ + file://CVE-2023-24531_2.patch \ " SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" diff --git a/meta/recipes-devtools/go/go-1.21/CVE-2023-24531_1.patch b/meta/recipes-devtools/go/go-1.21/CVE-2023-24531_1.patch new file mode 100644 index 0000000000..5f6d7e16a8 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.21/CVE-2023-24531_1.patch @@ -0,0 +1,252 @@ +From 0f717b5f7d32bb660c01ec0366bd53c9b4c5ab5d Mon Sep 17 00:00:00 2001 +From: Michael Matloob +Date: Mon, 24 Apr 2023 16:57:28 -0400 +Subject: [PATCH 1/2] cmd/go: sanitize go env outputs + +go env, without any arguments, outputs the environment variables in +the form of a script that can be run on the host OS. On Unix, single +quote the strings and place single quotes themselves outside the +single quoted strings. On windows use the set "var=val" syntax with +the quote starting before the variable. + +Fixes #58508 + +Change-Id: Iecd379a4af7285ea9b2024f0202250c74fd9a2bd +Reviewed-on: https://go-review.googlesource.com/c/go/+/488375 +TryBot-Result: Gopher Robot +Reviewed-by: Michael Matloob +Reviewed-by: Damien Neil +Run-TryBot: Michael Matloob +Reviewed-by: Bryan Mills +Reviewed-by: Quim Muntal + +CVE: CVE-2023-24531 +Upstream-Status: Backport [f379e78951a405e7e99a60fb231eeedbf976c108] + +Signed-off-by: Sakib Sajal +--- + src/cmd/go/internal/envcmd/env.go | 60 ++++++++++++- + src/cmd/go/internal/envcmd/env_test.go | 94 +++++++++++++++++++++ + src/cmd/go/testdata/script/env_sanitize.txt | 5 ++ + 3 files changed, 157 insertions(+), 2 deletions(-) + create mode 100644 src/cmd/go/internal/envcmd/env_test.go + create mode 100644 src/cmd/go/testdata/script/env_sanitize.txt + +diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go +index 43b94e7..0ce8843 100644 +--- a/src/cmd/go/internal/envcmd/env.go ++++ b/src/cmd/go/internal/envcmd/env.go +@@ -6,6 +6,7 @@ + package envcmd + + import ( ++ "bytes" + "context" + "encoding/json" + "fmt" +@@ -17,6 +18,7 @@ import ( + "runtime" + "sort" + "strings" ++ "unicode" + "unicode/utf8" + + "cmd/go/internal/base" +@@ -379,9 +381,12 @@ func checkBuildConfig(add map[string]string, del map[string]bool) error { + func PrintEnv(w io.Writer, env []cfg.EnvVar) { + for _, e := range env { + if e.Name != "TERM" { ++ if runtime.GOOS != "plan9" && bytes.Contains([]byte(e.Value), []byte{0}) { ++ base.Fatalf("go: internal error: encountered null byte in environment variable %s on non-plan9 platform", e.Name) ++ } + switch runtime.GOOS { + default: +- fmt.Fprintf(w, "%s=\"%s\"\n", e.Name, e.Value) ++ fmt.Fprintf(w, "%s=%s\n", e.Name, shellQuote(e.Value)) + case "plan9": + if strings.IndexByte(e.Value, '\x00') < 0 { + fmt.Fprintf(w, "%s='%s'\n", e.Name, strings.ReplaceAll(e.Value, "'", "''")) +@@ -392,17 +397,68 @@ func PrintEnv(w io.Writer, env []cfg.EnvVar) { + if x > 0 { + fmt.Fprintf(w, " ") + } ++ // TODO(#59979): Does this need to be quoted like above? + fmt.Fprintf(w, "%s", s) + } + fmt.Fprintf(w, ")\n") + } + case "windows": +- fmt.Fprintf(w, "set %s=%s\n", e.Name, e.Value) ++ if hasNonGraphic(e.Value) { ++ base.Errorf("go: stripping unprintable or unescapable characters from %%%q%%", e.Name) ++ } ++ fmt.Fprintf(w, "set %s=%s\n", e.Name, batchEscape(e.Value)) + } + } + } + } + ++func hasNonGraphic(s string) bool { ++ for _, c := range []byte(s) { ++ if c == '\r' || c == '\n' || (!unicode.IsGraphic(rune(c)) && !unicode.IsSpace(rune(c))) { ++ return true ++ } ++ } ++ return false ++} ++ ++func shellQuote(s string) string { ++ var b bytes.Buffer ++ b.WriteByte('\'') ++ for _, x := range []byte(s) { ++ if x == '\'' { ++ // Close the single quoted string, add an escaped single quote, ++ // and start another single quoted string. ++ b.WriteString(`'\''`) ++ } else { ++ b.WriteByte(x) ++ } ++ } ++ b.WriteByte('\'') ++ return b.String() ++} ++ ++func batchEscape(s string) string { ++ var b bytes.Buffer ++ for _, x := range []byte(s) { ++ if x == '\r' || x == '\n' || (!unicode.IsGraphic(rune(x)) && !unicode.IsSpace(rune(x))) { ++ b.WriteRune(unicode.ReplacementChar) ++ continue ++ } ++ switch x { ++ case '%': ++ b.WriteString("%%") ++ case '<', '>', '|', '&', '^': ++ // These are special characters that need to be escaped with ^. See ++ // https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/set_1. ++ b.WriteByte('^') ++ b.WriteByte(x) ++ default: ++ b.WriteByte(x) ++ } ++ } ++ return b.String() ++} ++ + func printEnvAsJSON(env []cfg.EnvVar) { + m := make(map[string]string) + for _, e := range env { +diff --git a/src/cmd/go/internal/envcmd/env_test.go b/src/cmd/go/internal/envcmd/env_test.go +new file mode 100644 +index 0000000..32d99fd +--- /dev/null ++++ b/src/cmd/go/internal/envcmd/env_test.go +@@ -0,0 +1,94 @@ ++// Copyright 2022 The Go Authors. All rights reserved. ++// Use of this source code is governed by a BSD-style ++// license that can be found in the LICENSE file. ++ ++//go:build unix || windows ++ ++package envcmd ++ ++import ( ++ "bytes" ++ "cmd/go/internal/cfg" ++ "fmt" ++ "internal/testenv" ++ "os" ++ "os/exec" ++ "path/filepath" ++ "runtime" ++ "testing" ++ "unicode" ++) ++ ++func FuzzPrintEnvEscape(f *testing.F) { ++ f.Add(`$(echo 'cc"'; echo 'OOPS="oops')`) ++ f.Add("$(echo shell expansion 1>&2)") ++ f.Add("''") ++ f.Add(`C:\"Program Files"\`) ++ f.Add(`\\"Quoted Host"\\share`) ++ f.Add("\xfb") ++ f.Add("0") ++ f.Add("") ++ f.Add("''''''''") ++ f.Add("\r") ++ f.Add("\n") ++ f.Add("E,%") ++ f.Fuzz(func(t *testing.T, s string) { ++ t.Parallel() ++ ++ for _, c := range []byte(s) { ++ if c == 0 { ++ t.Skipf("skipping %q: contains a null byte. Null bytes can't occur in the environment"+ ++ " outside of Plan 9, which has different code path than Windows and Unix that this test"+ ++ " isn't testing.", s) ++ } ++ if c > unicode.MaxASCII { ++ t.Skipf("skipping %#q: contains a non-ASCII character %q", s, c) ++ } ++ if !unicode.IsGraphic(rune(c)) && !unicode.IsSpace(rune(c)) { ++ t.Skipf("skipping %#q: contains non-graphic character %q", s, c) ++ } ++ if runtime.GOOS == "windows" && c == '\r' || c == '\n' { ++ t.Skipf("skipping %#q on Windows: contains unescapable character %q", s, c) ++ } ++ } ++ ++ var b bytes.Buffer ++ if runtime.GOOS == "windows" { ++ b.WriteString("@echo off\n") ++ } ++ PrintEnv(&b, []cfg.EnvVar{{Name: "var", Value: s}}) ++ var want string ++ if runtime.GOOS == "windows" { ++ fmt.Fprintf(&b, "echo \"%%var%%\"\n") ++ want += "\"" + s + "\"\r\n" ++ } else { ++ fmt.Fprintf(&b, "printf '%%s\\n' \"$var\"\n") ++ want += s + "\n" ++ } ++ scriptfilename := "script.sh" ++ if runtime.GOOS == "windows" { ++ scriptfilename = "script.bat" ++ } ++ scriptfile := filepath.Join(t.TempDir(), scriptfilename) ++ if err := os.WriteFile(scriptfile, b.Bytes(), 0777); err != nil { ++ t.Fatal(err) ++ } ++ t.Log(b.String()) ++ var cmd *exec.Cmd ++ if runtime.GOOS == "windows" { ++ cmd = testenv.Command(t, "cmd.exe", "/C", scriptfile) ++ } else { ++ cmd = testenv.Command(t, "sh", "-c", scriptfile) ++ } ++ out, err := cmd.Output() ++ t.Log(string(out)) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ if string(out) != want { ++ t.Fatalf("output of running PrintEnv script and echoing variable: got: %q, want: %q", ++ string(out), want) ++ } ++ }) ++} +diff --git a/src/cmd/go/testdata/script/env_sanitize.txt b/src/cmd/go/testdata/script/env_sanitize.txt +new file mode 100644 +index 0000000..cc4d23a +--- /dev/null ++++ b/src/cmd/go/testdata/script/env_sanitize.txt +@@ -0,0 +1,5 @@ ++env GOFLAGS='$(echo ''cc"''; echo ''OOPS="oops'')' ++go env ++[GOOS:darwin] stdout 'GOFLAGS=''\$\(echo ''\\''''cc"''\\''''; echo ''\\''''OOPS="oops''\\''''\)''' ++[GOOS:linux] stdout 'GOFLAGS=''\$\(echo ''\\''''cc"''\\''''; echo ''\\''''OOPS="oops''\\''''\)''' ++[GOOS:windows] stdout 'set GOFLAGS=\$\(echo ''cc"''; echo ''OOPS="oops''\)' +-- +2.35.5 + diff --git a/meta/recipes-devtools/go/go-1.21/CVE-2023-24531_2.patch b/meta/recipes-devtools/go/go-1.21/CVE-2023-24531_2.patch new file mode 100644 index 0000000000..eecc04c2e3 --- /dev/null +++ b/meta/recipes-devtools/go/go-1.21/CVE-2023-24531_2.patch @@ -0,0 +1,47 @@ +From b2624f973692ca093348395c2418d1c422f2a162 Mon Sep 17 00:00:00 2001 +From: miller +Date: Mon, 8 May 2023 16:56:21 +0100 +Subject: [PATCH 2/2] cmd/go: quote entries in list-valued variables for go env + in plan9 + +When 'go env' without an argument prints environment variables as +a script which can be executed by the shell, variables with a +list value in Plan 9 (such as GOPATH) need to be printed with each +element enclosed in single quotes in case it contains characters +significant to the Plan 9 shell (such as ' ' or '='). + +For #58508 + +Change-Id: Ia30f51307cc6d07a7e3ada6bf9d60bf9951982ff +Reviewed-on: https://go-review.googlesource.com/c/go/+/493535 +Run-TryBot: Cherry Mui +Reviewed-by: Cherry Mui +Reviewed-by: Russ Cox +TryBot-Result: Gopher Robot +Auto-Submit: Dmitri Shuralyov + +CVE: CVE-2023-24531 +Upstream-Status: Backport [05cc9e55876874462a4726ca0101c970838c80e5] + +Signed-off-by: Sakib Sajal +--- + src/cmd/go/internal/envcmd/env.go | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/cmd/go/internal/envcmd/env.go b/src/cmd/go/internal/envcmd/env.go +index 0ce8843..b48d0bd 100644 +--- a/src/cmd/go/internal/envcmd/env.go ++++ b/src/cmd/go/internal/envcmd/env.go +@@ -397,8 +397,7 @@ func PrintEnv(w io.Writer, env []cfg.EnvVar) { + if x > 0 { + fmt.Fprintf(w, " ") + } +- // TODO(#59979): Does this need to be quoted like above? +- fmt.Fprintf(w, "%s", s) ++ fmt.Fprintf(w, "'%s'", strings.ReplaceAll(s, "'", "''")) + } + fmt.Fprintf(w, ")\n") + } +-- +2.35.5 + From patchwork Wed Aug 2 00:18:13 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Sakib Sajal X-Patchwork-Id: 28292 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 505E3C41513 for ; Wed, 2 Aug 2023 00:18:35 +0000 (UTC) Received: from mx0a-0064b401.pphosted.com (mx0a-0064b401.pphosted.com [205.220.166.238]) by mx.groups.io with SMTP id smtpd.web10.3939.1690935504581038244 for ; Tue, 01 Aug 2023 17:18:26 -0700 Authentication-Results: mx.groups.io; dkim=fail reason="dkim: body hash did not verify" header.i=@windriver.com header.s=PPS06212021 header.b=YvHI2Yrz; spf=permerror, err=parse error for token &{10 18 %{ir}.%{v}.%{d}.spf.has.pphosted.com}: invalid domain name (domain: windriver.com, ip: 205.220.166.238, mailfrom: prvs=757808ff50=sakib.sajal@windriver.com) Received: from pps.filterd (m0250810.ppops.net [127.0.0.1]) by mx0a-0064b401.pphosted.com (8.17.1.19/8.17.1.19) with ESMTP id 37201w2b006115 for ; Tue, 1 Aug 2023 17:18:22 -0700 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=windriver.com; h=from:to:subject:date:message-id:in-reply-to:references :mime-version:content-type:content-transfer-encoding; s= PPS06212021; bh=DyhVrsYd54prEf9rAxHsOY5eJGXh3OGd07pOfwQIv/4=; b= YvHI2YrzztQ+VZUcHqIyQsQ0IuPfKplu6mLEmxMaLGnEwVn99oqFD68HbEmzVR8c dy6+0TVsDmKZIhBEOrbQNf1e2LYMDTRl4x6VwWq23LzYmyKzu7lWEGs7wySLf+2i IhCe1xvo2zJCgst9EOEzPspqQ5+qV5Bby7X3oZNP2NSV8SZP5fk1r9e4OChuldNo orcwSd6zDPFLlPojRIOjgKrV2qihdmWTX8YNQGQxDt6bwzFyxZIFD3LIPDB1YLh/ NOryNdawnFKZEKGpxmUkA2qw2SuS3+vCz2Hy63OsJdbVm9l6FfQ7sEve9FeoWbyd F5R5yxPjmRE6PR3CxBGITg== Received: from ala-exchng02.corp.ad.wrs.com (ala-exchng02.wrs.com [147.11.82.254]) by mx0a-0064b401.pphosted.com (PPS) with ESMTPS id 3s6daqsp6x-1 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128 verify=NOT) for ; Tue, 01 Aug 2023 17:18:22 -0700 (PDT) Received: from ala-exchng01.corp.ad.wrs.com (147.11.82.252) by ALA-EXCHNG02.corp.ad.wrs.com (147.11.82.254) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) id 15.1.2507.27; Tue, 1 Aug 2023 17:18:21 -0700 Received: from ala-lpggp3.wrs.com (147.11.105.124) by ala-exchng01.corp.ad.wrs.com (147.11.82.252) with Microsoft SMTP Server id 15.1.2507.27 via Frontend Transport; Tue, 1 Aug 2023 17:18:21 -0700 From: Sakib Sajal To: Subject: [kirkstone][PATCH 3/3] go: fix CVE-2023-29406 Date: Tue, 1 Aug 2023 17:18:13 -0700 Message-ID: <20230802001813.3521965-3-sakib.sajal@windriver.com> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230802001813.3521965-1-sakib.sajal@windriver.com> References: <20230802001813.3521965-1-sakib.sajal@windriver.com> MIME-Version: 1.0 X-Proofpoint-ORIG-GUID: g8seb_4iy_7a0EjOZfVNkLZ6AfXdgQ0W X-Proofpoint-GUID: g8seb_4iy_7a0EjOZfVNkLZ6AfXdgQ0W X-Proofpoint-Virus-Version: vendor=baseguard engine=ICAP:2.0.254,Aquarius:18.0.957,Hydra:6.0.591,FMLib:17.11.176.26 definitions=2023-08-01_21,2023-08-01_01,2023-05-22_02 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 mlxscore=0 impostorscore=0 mlxlogscore=977 malwarescore=0 clxscore=1015 bulkscore=0 lowpriorityscore=0 phishscore=0 spamscore=0 adultscore=0 suspectscore=0 priorityscore=1501 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.19.0-2306200000 definitions=main-2308020000 X-MIME-Autoconverted: from 8bit to quoted-printable by mx0a-0064b401.pphosted.com id 37201w2b006115 List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Wed, 02 Aug 2023 00:18:35 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/185367 Backport required patch to fix CVE-2023-29406. Signed-off-by: Sakib Sajal --- meta/recipes-devtools/go/go-1.17.13.inc | 1 + .../go/go-1.19/CVE-2023-29406.patch | 210 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 meta/recipes-devtools/go/go-1.19/CVE-2023-29406.patch diff --git a/meta/recipes-devtools/go/go-1.17.13.inc b/meta/recipes-devtools/go/go-1.17.13.inc index d46eab01a0..b9d905a616 100644 --- a/meta/recipes-devtools/go/go-1.17.13.inc +++ b/meta/recipes-devtools/go/go-1.17.13.inc @@ -41,6 +41,7 @@ SRC_URI += "\ file://CVE-2023-24536_3.patch \ file://CVE-2023-24531_1.patch \ file://CVE-2023-24531_2.patch \ + file://CVE-2023-29406.patch \ " SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd" diff --git a/meta/recipes-devtools/go/go-1.19/CVE-2023-29406.patch b/meta/recipes-devtools/go/go-1.19/CVE-2023-29406.patch new file mode 100644 index 0000000000..8a82d5c18c --- /dev/null +++ b/meta/recipes-devtools/go/go-1.19/CVE-2023-29406.patch @@ -0,0 +1,210 @@ +From 3077df09879fb1aad0ccfc009fee6fc5e5f532bc Mon Sep 17 00:00:00 2001 +From: Damien Neil +Date: Wed, 28 Jun 2023 13:20:08 -0700 +Subject: [PATCH] net/http: validate Host header before sending + +Verify that the Host header we send is valid. +Avoids surprising behavior such as a Host of "go.dev\r\nX-Evil:oops" +adding an X-Evil header to HTTP/1 requests. + +Add a test, skip the test for HTTP/2. HTTP/2 is not vulnerable to +header injection in the way HTTP/1 is, but x/net/http2 doesn't validate +the header and will go into a retry loop when the server rejects it. +CL 506995 adds the necessary validation to x/net/http2. + +Updates #60374 +Fixes #61075 +For CVE-2023-29406 + +Change-Id: I05cb6866a9bead043101954dfded199258c6dd04 +Reviewed-on: https://go-review.googlesource.com/c/go/+/506996 +Reviewed-by: Tatiana Bradley +TryBot-Result: Gopher Robot +Run-TryBot: Damien Neil +(cherry picked from commit 499458f7ca04087958987a33c2703c3ef03e27e2) +Reviewed-on: https://go-review.googlesource.com/c/go/+/507358 +Run-TryBot: Tatiana Bradley +Reviewed-by: Roland Shoemaker + +CVE: CVE-2023-29406 +Upstream-Status: Backport [5fa6923b1ea891400153d04ddf1545e23b40041b] +Signed-off-by: Sakib Sajal +--- + src/net/http/http_test.go | 29 ---------------------- + src/net/http/request.go | 45 ++++++++-------------------------- + src/net/http/request_test.go | 11 ++------- + src/net/http/transport_test.go | 18 ++++++++++++++ + 4 files changed, 30 insertions(+), 73 deletions(-) + +diff --git a/src/net/http/http_test.go b/src/net/http/http_test.go +index 0d92fe5..f03272a 100644 +--- a/src/net/http/http_test.go ++++ b/src/net/http/http_test.go +@@ -48,35 +48,6 @@ func TestForeachHeaderElement(t *testing.T) { + } + } + +-func TestCleanHost(t *testing.T) { +- tests := []struct { +- in, want string +- }{ +- {"www.google.com", "www.google.com"}, +- {"www.google.com foo", "www.google.com"}, +- {"www.google.com/foo", "www.google.com"}, +- {" first character is a space", ""}, +- {"[1::6]:8080", "[1::6]:8080"}, +- +- // Punycode: +- {"гофер.рф/foo", "xn--c1ae0ajs.xn--p1ai"}, +- {"bücher.de", "xn--bcher-kva.de"}, +- {"bücher.de:8080", "xn--bcher-kva.de:8080"}, +- // Verify we convert to lowercase before punycode: +- {"BÜCHER.de", "xn--bcher-kva.de"}, +- {"BÜCHER.de:8080", "xn--bcher-kva.de:8080"}, +- // Verify we normalize to NFC before punycode: +- {"gophér.nfc", "xn--gophr-esa.nfc"}, // NFC input; no work needed +- {"goph\u0065\u0301r.nfd", "xn--gophr-esa.nfd"}, // NFD input +- } +- for _, tt := range tests { +- got := cleanHost(tt.in) +- if tt.want != got { +- t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want) +- } +- } +-} +- + // Test that cmd/go doesn't link in the HTTP server. + // + // This catches accidental dependencies between the HTTP transport and +diff --git a/src/net/http/request.go b/src/net/http/request.go +index 09cb0c7..2f4e740 100644 +--- a/src/net/http/request.go ++++ b/src/net/http/request.go +@@ -17,7 +17,6 @@ import ( + "io" + "mime" + "mime/multipart" +- "net" + "net/http/httptrace" + "net/http/internal/ascii" + "net/textproto" +@@ -27,6 +26,7 @@ import ( + "strings" + "sync" + ++ "golang.org/x/net/http/httpguts" + "golang.org/x/net/idna" + ) + +@@ -568,12 +568,19 @@ func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitF + // is not given, use the host from the request URL. + // + // Clean the host, in case it arrives with unexpected stuff in it. +- host := cleanHost(r.Host) ++ host := r.Host + if host == "" { + if r.URL == nil { + return errMissingHost + } +- host = cleanHost(r.URL.Host) ++ host = r.URL.Host ++ } ++ host, err = httpguts.PunycodeHostPort(host) ++ if err != nil { ++ return err ++ } ++ if !httpguts.ValidHostHeader(host) { ++ return errors.New("http: invalid Host header") + } + + // According to RFC 6874, an HTTP client, proxy, or other +@@ -730,38 +737,6 @@ func idnaASCII(v string) (string, error) { + return idna.Lookup.ToASCII(v) + } + +-// cleanHost cleans up the host sent in request's Host header. +-// +-// It both strips anything after '/' or ' ', and puts the value +-// into Punycode form, if necessary. +-// +-// Ideally we'd clean the Host header according to the spec: +-// https://tools.ietf.org/html/rfc7230#section-5.4 (Host = uri-host [ ":" port ]") +-// https://tools.ietf.org/html/rfc7230#section-2.7 (uri-host -> rfc3986's host) +-// https://tools.ietf.org/html/rfc3986#section-3.2.2 (definition of host) +-// But practically, what we are trying to avoid is the situation in +-// issue 11206, where a malformed Host header used in the proxy context +-// would create a bad request. So it is enough to just truncate at the +-// first offending character. +-func cleanHost(in string) string { +- if i := strings.IndexAny(in, " /"); i != -1 { +- in = in[:i] +- } +- host, port, err := net.SplitHostPort(in) +- if err != nil { // input was just a host +- a, err := idnaASCII(in) +- if err != nil { +- return in // garbage in, garbage out +- } +- return a +- } +- a, err := idnaASCII(host) +- if err != nil { +- return in // garbage in, garbage out +- } +- return net.JoinHostPort(a, port) +-} +- + // removeZone removes IPv6 zone identifier from host. + // E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080" + func removeZone(host string) string { +diff --git a/src/net/http/request_test.go b/src/net/http/request_test.go +index fac12b7..368e87a 100644 +--- a/src/net/http/request_test.go ++++ b/src/net/http/request_test.go +@@ -776,15 +776,8 @@ func TestRequestBadHost(t *testing.T) { + } + req.Host = "foo.com with spaces" + req.URL.Host = "foo.com with spaces" +- req.Write(logWrites{t, &got}) +- want := []string{ +- "GET /after HTTP/1.1\r\n", +- "Host: foo.com\r\n", +- "User-Agent: " + DefaultUserAgent + "\r\n", +- "\r\n", +- } +- if !reflect.DeepEqual(got, want) { +- t.Errorf("Writes = %q\n Want = %q", got, want) ++ if err := req.Write(logWrites{t, &got}); err == nil { ++ t.Errorf("Writing request with invalid Host: succeded, want error") + } + } + +diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go +index eeaa492..58f12af 100644 +--- a/src/net/http/transport_test.go ++++ b/src/net/http/transport_test.go +@@ -6512,3 +6512,21 @@ func TestCancelRequestWhenSharingConnection(t *testing.T) { + close(r2c) + wg.Wait() + } ++ ++func TestRequestSanitization(t *testing.T) { ++ setParallel(t) ++ defer afterTest(t) ++ ++ ts := newClientServerTest(t, h1Mode, HandlerFunc(func(rw ResponseWriter, req *Request) { ++ if h, ok := req.Header["X-Evil"]; ok { ++ t.Errorf("request has X-Evil header: %q", h) ++ } ++ })).ts ++ defer ts.Close() ++ req, _ := NewRequest("GET", ts.URL, nil) ++ req.Host = "go.dev\r\nX-Evil:evil" ++ resp, _ := ts.Client().Do(req) ++ if resp != nil { ++ resp.Body.Close() ++ } ++} +-- +2.35.5 +