public inbox for openembedded-core@lists.openembedded.org
 help / color / mirror / Atom feed
From: Sakib Sajal <sakib.sajal@windriver.com>
To: Steve Sakoman <steve@sakoman.com>
Cc: openembedded-core@lists.openembedded.org
Subject: Re: [OE-core] [kirkstone][PATCH v2] go: fix CVE-2022-2879 and CVE-2022-41720
Date: Mon, 27 Mar 2023 18:26:36 -0400	[thread overview]
Message-ID: <c4fcf1ab-3cf5-03f3-9fc2-36ee9f243634@windriver.com> (raw)
In-Reply-To: <CAOSpxdb6Lo=TxqXNSjCR5mrHdCsw419wmf_be+oQYQYxJgs0Ng@mail.gmail.com>


On 2023-03-22 12:21, Steve Sakoman wrote:
> CAUTION: This email comes from a non Wind River email account!
> Do not click links or open attachments unless you recognize the sender and know the content is safe.
>
> On Tue, Mar 21, 2023 at 9:36 AM Sakib Sajal <sakib.sajal@windriver.com> wrote:
>> Backport appropriate patches to fix CVE-2022-2879 and CVE-2022-41720.
>>
>> Modified the original fix for CVE-2022-2879 to remove a testdata tarball
>> and any references to it since git binary diffs are not supported in
>> quilt.
>>
>> Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com>
>> ---
>>   meta/recipes-devtools/go/go-1.17.13.inc       |   2 +
>>   ...01-archive-tar-limit-size-of-headers.patch | 177 ++++++
>>   ...d-escapes-from-os.DirFS-and-http.Dir.patch | 514 ++++++++++++++++++
>>   3 files changed, 693 insertions(+)
>>   create mode 100644 meta/recipes-devtools/go/go-1.18/0001-archive-tar-limit-size-of-headers.patch
>>   create mode 100644 meta/recipes-devtools/go/go-1.18/0002-os-net-http-avoid-escapes-from-os.DirFS-and-http.Dir.patch
>>
>> diff --git a/meta/recipes-devtools/go/go-1.17.13.inc b/meta/recipes-devtools/go/go-1.17.13.inc
>> index 99662bd298..a6081bdee7 100644
>> --- a/meta/recipes-devtools/go/go-1.17.13.inc
>> +++ b/meta/recipes-devtools/go/go-1.17.13.inc
>> @@ -20,6 +20,8 @@ SRC_URI += "\
>>       file://0001-net-http-httputil-avoid-query-parameter-smuggling.patch \
>>       file://CVE-2022-41715.patch \
>>       file://CVE-2022-41717.patch \
>> +    file://0001-archive-tar-limit-size-of-headers.patch \
>> +    file://0002-os-net-http-avoid-escapes-from-os.DirFS-and-http.Dir.patch \
> Could you please resubmit with the patch file names changed to reflect
> the CVE they are fixing? i.e.
>
>        file://CVE-2022-2879.patch \
>        file://CVE-2022-41720.patch \
>
> Thanks!
>
> Steve

Done!

Sakib

>
>>   "
>>   SRC_URI[main.sha256sum] = "a1a48b23afb206f95e7bbaa9b898d965f90826f6f1d1fc0c1d784ada0cd300fd"
>>
>> diff --git a/meta/recipes-devtools/go/go-1.18/0001-archive-tar-limit-size-of-headers.patch b/meta/recipes-devtools/go/go-1.18/0001-archive-tar-limit-size-of-headers.patch
>> new file mode 100644
>> index 0000000000..0315e1a3ee
>> --- /dev/null
>> +++ b/meta/recipes-devtools/go/go-1.18/0001-archive-tar-limit-size-of-headers.patch
>> @@ -0,0 +1,177 @@
>> +From d064ed520a7cc6b480f9565e30751e695d394f4e Mon Sep 17 00:00:00 2001
>> +From: Damien Neil <dneil@google.com>
>> +Date: Fri, 2 Sep 2022 20:45:18 -0700
>> +Subject: [PATCH] archive/tar: limit size of headers
>> +
>> +Set a 1MiB limit on special file blocks (PAX headers, GNU long names,
>> +GNU link names), to avoid reading arbitrarily large amounts of data
>> +into memory.
>> +
>> +Thanks to Adam Korczynski (ADA Logics) and OSS-Fuzz for reporting
>> +this issue.
>> +
>> +Fixes CVE-2022-2879
>> +Updates #54853
>> +Fixes #55925
>> +
>> +Change-Id: I85136d6ff1e0af101a112190e027987ab4335680
>> +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1565555
>> +Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
>> +Run-TryBot: Roland Shoemaker <bracewell@google.com>
>> +Reviewed-by: Roland Shoemaker <bracewell@google.com>
>> +(cherry picked from commit 6ee768cef6b82adf7a90dcf367a1699ef694f3b2)
>> +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1590622
>> +Reviewed-by: Damien Neil <dneil@google.com>
>> +Reviewed-by: Julie Qiu <julieqiu@google.com>
>> +Reviewed-on: https://go-review.googlesource.com/c/go/+/438500
>> +Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
>> +Reviewed-by: Carlos Amedee <carlos@golang.org>
>> +Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
>> +Run-TryBot: Carlos Amedee <carlos@golang.org>
>> +TryBot-Result: Gopher Robot <gobot@golang.org>
>> +
>> +CVE: CVE-2022-2879
>> +Upstream-Status: Backport [0a723816cd205576945fa57fbdde7e6532d59d08]
>> +Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com>
>> +---
>> + src/archive/tar/format.go      |  4 ++++
>> + src/archive/tar/reader.go      | 14 ++++++++++++--
>> + src/archive/tar/reader_test.go |  8 +++++++-
>> + src/archive/tar/writer.go      |  3 +++
>> + src/archive/tar/writer_test.go | 27 +++++++++++++++++++++++++++
>> + 5 files changed, 53 insertions(+), 3 deletions(-)
>> +
>> +diff --git a/src/archive/tar/format.go b/src/archive/tar/format.go
>> +index cfe24a5..6642364 100644
>> +--- a/src/archive/tar/format.go
>> ++++ b/src/archive/tar/format.go
>> +@@ -143,6 +143,10 @@ const (
>> +       blockSize  = 512 // Size of each block in a tar stream
>> +       nameSize   = 100 // Max length of the name field in USTAR format
>> +       prefixSize = 155 // Max length of the prefix field in USTAR format
>> ++
>> ++      // Max length of a special file (PAX header, GNU long name or link).
>> ++      // This matches the limit used by libarchive.
>> ++      maxSpecialFileSize = 1 << 20
>> + )
>> +
>> + // blockPadding computes the number of bytes needed to pad offset up to the
>> +diff --git a/src/archive/tar/reader.go b/src/archive/tar/reader.go
>> +index 1b1d5b4..f645af8 100644
>> +--- a/src/archive/tar/reader.go
>> ++++ b/src/archive/tar/reader.go
>> +@@ -103,7 +103,7 @@ func (tr *Reader) next() (*Header, error) {
>> +                       continue // This is a meta header affecting the next header
>> +               case TypeGNULongName, TypeGNULongLink:
>> +                       format.mayOnlyBe(FormatGNU)
>> +-                      realname, err := io.ReadAll(tr)
>> ++                      realname, err := readSpecialFile(tr)
>> +                       if err != nil {
>> +                               return nil, err
>> +                       }
>> +@@ -293,7 +293,7 @@ func mergePAX(hdr *Header, paxHdrs map[string]string) (err error) {
>> + // parsePAX parses PAX headers.
>> + // If an extended header (type 'x') is invalid, ErrHeader is returned
>> + func parsePAX(r io.Reader) (map[string]string, error) {
>> +-      buf, err := io.ReadAll(r)
>> ++      buf, err := readSpecialFile(r)
>> +       if err != nil {
>> +               return nil, err
>> +       }
>> +@@ -826,6 +826,16 @@ func tryReadFull(r io.Reader, b []byte) (n int, err error) {
>> +       return n, err
>> + }
>> +
>> ++// readSpecialFile is like io.ReadAll except it returns
>> ++// ErrFieldTooLong if more than maxSpecialFileSize is read.
>> ++func readSpecialFile(r io.Reader) ([]byte, error) {
>> ++      buf, err := io.ReadAll(io.LimitReader(r, maxSpecialFileSize+1))
>> ++      if len(buf) > maxSpecialFileSize {
>> ++              return nil, ErrFieldTooLong
>> ++      }
>> ++      return buf, err
>> ++}
>> ++
>> + // discard skips n bytes in r, reporting an error if unable to do so.
>> + func discard(r io.Reader, n int64) error {
>> +       // If possible, Seek to the last byte before the end of the data section.
>> +diff --git a/src/archive/tar/reader_test.go b/src/archive/tar/reader_test.go
>> +index 789ddc1..926dc3d 100644
>> +--- a/src/archive/tar/reader_test.go
>> ++++ b/src/archive/tar/reader_test.go
>> +@@ -6,6 +6,7 @@ package tar
>> +
>> + import (
>> +       "bytes"
>> ++      "compress/bzip2"
>> +       "crypto/md5"
>> +       "errors"
>> +       "fmt"
>> +@@ -625,9 +626,14 @@ func TestReader(t *testing.T) {
>> +                       }
>> +                       defer f.Close()
>> +
>> ++                      var fr io.Reader = f
>> ++                      if strings.HasSuffix(v.file, ".bz2") {
>> ++                              fr = bzip2.NewReader(fr)
>> ++                      }
>> ++
>> +                       // Capture all headers and checksums.
>> +                       var (
>> +-                              tr      = NewReader(f)
>> ++                              tr      = NewReader(fr)
>> +                               hdrs    []*Header
>> +                               chksums []string
>> +                               rdbuf   = make([]byte, 8)
>> +diff --git a/src/archive/tar/writer.go b/src/archive/tar/writer.go
>> +index e80498d..893eac0 100644
>> +--- a/src/archive/tar/writer.go
>> ++++ b/src/archive/tar/writer.go
>> +@@ -199,6 +199,9 @@ func (tw *Writer) writePAXHeader(hdr *Header, paxHdrs map[string]string) error {
>> +                       flag = TypeXHeader
>> +               }
>> +               data := buf.String()
>> ++              if len(data) > maxSpecialFileSize {
>> ++                      return ErrFieldTooLong
>> ++              }
>> +               if err := tw.writeRawFile(name, data, flag, FormatPAX); err != nil || isGlobal {
>> +                       return err // Global headers return here
>> +               }
>> +diff --git a/src/archive/tar/writer_test.go b/src/archive/tar/writer_test.go
>> +index a00f02d..4e709e5 100644
>> +--- a/src/archive/tar/writer_test.go
>> ++++ b/src/archive/tar/writer_test.go
>> +@@ -1006,6 +1006,33 @@ func TestIssue12594(t *testing.T) {
>> +       }
>> + }
>> +
>> ++func TestWriteLongHeader(t *testing.T) {
>> ++      for _, test := range []struct {
>> ++              name string
>> ++              h    *Header
>> ++      }{{
>> ++              name: "name too long",
>> ++              h:    &Header{Name: strings.Repeat("a", maxSpecialFileSize)},
>> ++      }, {
>> ++              name: "linkname too long",
>> ++              h:    &Header{Linkname: strings.Repeat("a", maxSpecialFileSize)},
>> ++      }, {
>> ++              name: "uname too long",
>> ++              h:    &Header{Uname: strings.Repeat("a", maxSpecialFileSize)},
>> ++      }, {
>> ++              name: "gname too long",
>> ++              h:    &Header{Gname: strings.Repeat("a", maxSpecialFileSize)},
>> ++      }, {
>> ++              name: "PAX header too long",
>> ++              h:    &Header{PAXRecords: map[string]string{"GOLANG.x": strings.Repeat("a", maxSpecialFileSize)}},
>> ++      }} {
>> ++              w := NewWriter(io.Discard)
>> ++              if err := w.WriteHeader(test.h); err != ErrFieldTooLong {
>> ++                      t.Errorf("%v: w.WriteHeader() = %v, want ErrFieldTooLong", test.name, err)
>> ++              }
>> ++      }
>> ++}
>> ++
>> + // testNonEmptyWriter wraps an io.Writer and ensures that
>> + // Write is never called with an empty buffer.
>> + type testNonEmptyWriter struct{ io.Writer }
>> diff --git a/meta/recipes-devtools/go/go-1.18/0002-os-net-http-avoid-escapes-from-os.DirFS-and-http.Dir.patch b/meta/recipes-devtools/go/go-1.18/0002-os-net-http-avoid-escapes-from-os.DirFS-and-http.Dir.patch
>> new file mode 100644
>> index 0000000000..6c2e8804b3
>> --- /dev/null
>> +++ b/meta/recipes-devtools/go/go-1.18/0002-os-net-http-avoid-escapes-from-os.DirFS-and-http.Dir.patch
>> @@ -0,0 +1,514 @@
>> +From f8896a97a0630b0f2f8c488310147f7f20b3ec7d Mon Sep 17 00:00:00 2001
>> +From: Damien Neil <dneil@google.com>
>> +Date: Thu, 10 Nov 2022 12:16:27 -0800
>> +Subject: [PATCH] os, net/http: avoid escapes from os.DirFS and http.Dir on
>> + Windows
>> +
>> +Do not permit access to Windows reserved device names (NUL, COM1, etc.)
>> +via os.DirFS and http.Dir filesystems.
>> +
>> +Avoid escapes from os.DirFS(`\`) on Windows. DirFS would join the
>> +the root to the relative path with a path separator, making
>> +os.DirFS(`\`).Open(`/foo/bar`) open the path `\\foo\bar`, which is
>> +a UNC name. Not only does this not open the intended file, but permits
>> +reference to any file on the system rather than only files on the
>> +current drive.
>> +
>> +Make os.DirFS("") invalid, with all file access failing. Previously,
>> +a root of "" was interpreted as "/", which is surprising and probably
>> +unintentional.
>> +
>> +Fixes CVE-2022-41720.
>> +Fixes #56694.
>> +
>> +Change-Id: I275b5fa391e6ad7404309ea98ccc97405942e0f0
>> +Reviewed-on: https://team-review.git.corp.google.com/c/golang/go-private/+/1663832
>> +Reviewed-by: Julie Qiu <julieqiu@google.com>
>> +Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
>> +Reviewed-on: https://go-review.googlesource.com/c/go/+/455360
>> +Reviewed-by: Michael Pratt <mpratt@google.com>
>> +TryBot-Result: Gopher Robot <gobot@golang.org>
>> +Run-TryBot: Jenny Rakoczy <jenny@golang.org>
>> +
>> +CVE: CVE-2022-41720
>> +Upstream-Status: Backport [7013a4f5f816af62033ad63dd06b77c30d7a62a7]
>> +Signed-off-by: Sakib Sajal <sakib.sajal@windriver.com>
>> +---
>> + src/go/build/deps_test.go                 |  1 +
>> + src/internal/safefilepath/path.go         | 21 +++++
>> + src/internal/safefilepath/path_other.go   | 23 ++++++
>> + src/internal/safefilepath/path_test.go    | 88 +++++++++++++++++++++
>> + src/internal/safefilepath/path_windows.go | 95 +++++++++++++++++++++++
>> + src/net/http/fs.go                        |  8 +-
>> + src/net/http/fs_test.go                   | 28 +++++++
>> + src/os/file.go                            | 36 +++++++--
>> + src/os/os_test.go                         | 38 +++++++++
>> + 9 files changed, 328 insertions(+), 10 deletions(-)
>> + create mode 100644 src/internal/safefilepath/path.go
>> + create mode 100644 src/internal/safefilepath/path_other.go
>> + create mode 100644 src/internal/safefilepath/path_test.go
>> + create mode 100644 src/internal/safefilepath/path_windows.go
>> +
>> +diff --git a/src/go/build/deps_test.go b/src/go/build/deps_test.go
>> +index 45e2f25..dc3bb8c 100644
>> +--- a/src/go/build/deps_test.go
>> ++++ b/src/go/build/deps_test.go
>> +@@ -165,6 +165,7 @@ var depsRules = `
>> +       io/fs
>> +       < internal/testlog
>> +       < internal/poll
>> ++      < internal/safefilepath
>> +       < os
>> +       < os/signal;
>> +
>> +diff --git a/src/internal/safefilepath/path.go b/src/internal/safefilepath/path.go
>> +new file mode 100644
>> +index 0000000..0f0a270
>> +--- /dev/null
>> ++++ b/src/internal/safefilepath/path.go
>> +@@ -0,0 +1,21 @@
>> ++// 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.
>> ++
>> ++// Package safefilepath manipulates operating-system file paths.
>> ++package safefilepath
>> ++
>> ++import (
>> ++      "errors"
>> ++)
>> ++
>> ++var errInvalidPath = errors.New("invalid path")
>> ++
>> ++// FromFS converts a slash-separated path into an operating-system path.
>> ++//
>> ++// FromFS returns an error if the path cannot be represented by the operating
>> ++// system. For example, paths containing '\' and ':' characters are rejected
>> ++// on Windows.
>> ++func FromFS(path string) (string, error) {
>> ++      return fromFS(path)
>> ++}
>> +diff --git a/src/internal/safefilepath/path_other.go b/src/internal/safefilepath/path_other.go
>> +new file mode 100644
>> +index 0000000..f93da18
>> +--- /dev/null
>> ++++ b/src/internal/safefilepath/path_other.go
>> +@@ -0,0 +1,23 @@
>> ++// 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 !windows
>> ++
>> ++package safefilepath
>> ++
>> ++import "runtime"
>> ++
>> ++func fromFS(path string) (string, error) {
>> ++      if runtime.GOOS == "plan9" {
>> ++              if len(path) > 0 && path[0] == '#' {
>> ++                      return path, errInvalidPath
>> ++              }
>> ++      }
>> ++      for i := range path {
>> ++              if path[i] == 0 {
>> ++                      return "", errInvalidPath
>> ++              }
>> ++      }
>> ++      return path, nil
>> ++}
>> +diff --git a/src/internal/safefilepath/path_test.go b/src/internal/safefilepath/path_test.go
>> +new file mode 100644
>> +index 0000000..dc662c1
>> +--- /dev/null
>> ++++ b/src/internal/safefilepath/path_test.go
>> +@@ -0,0 +1,88 @@
>> ++// 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.
>> ++
>> ++package safefilepath_test
>> ++
>> ++import (
>> ++      "internal/safefilepath"
>> ++      "os"
>> ++      "path/filepath"
>> ++      "runtime"
>> ++      "testing"
>> ++)
>> ++
>> ++type PathTest struct {
>> ++      path, result string
>> ++}
>> ++
>> ++const invalid = ""
>> ++
>> ++var fspathtests = []PathTest{
>> ++      {".", "."},
>> ++      {"/a/b/c", "/a/b/c"},
>> ++      {"a\x00b", invalid},
>> ++}
>> ++
>> ++var winreservedpathtests = []PathTest{
>> ++      {`a\b`, `a\b`},
>> ++      {`a:b`, `a:b`},
>> ++      {`a/b:c`, `a/b:c`},
>> ++      {`NUL`, `NUL`},
>> ++      {`./com1`, `./com1`},
>> ++      {`a/nul/b`, `a/nul/b`},
>> ++}
>> ++
>> ++// Whether a reserved name with an extension is reserved or not varies by
>> ++// Windows version.
>> ++var winreservedextpathtests = []PathTest{
>> ++      {"nul.txt", "nul.txt"},
>> ++      {"a/nul.txt/b", "a/nul.txt/b"},
>> ++}
>> ++
>> ++var plan9reservedpathtests = []PathTest{
>> ++      {`#c`, `#c`},
>> ++}
>> ++
>> ++func TestFromFS(t *testing.T) {
>> ++      switch runtime.GOOS {
>> ++      case "windows":
>> ++              if canWriteFile(t, "NUL") {
>> ++                      t.Errorf("can unexpectedly write a file named NUL on Windows")
>> ++              }
>> ++              if canWriteFile(t, "nul.txt") {
>> ++                      fspathtests = append(fspathtests, winreservedextpathtests...)
>> ++              } else {
>> ++                      winreservedpathtests = append(winreservedpathtests, winreservedextpathtests...)
>> ++              }
>> ++              for i := range winreservedpathtests {
>> ++                      winreservedpathtests[i].result = invalid
>> ++              }
>> ++              for i := range fspathtests {
>> ++                      fspathtests[i].result = filepath.FromSlash(fspathtests[i].result)
>> ++              }
>> ++      case "plan9":
>> ++              for i := range plan9reservedpathtests {
>> ++                      plan9reservedpathtests[i].result = invalid
>> ++              }
>> ++      }
>> ++      tests := fspathtests
>> ++      tests = append(tests, winreservedpathtests...)
>> ++      tests = append(tests, plan9reservedpathtests...)
>> ++      for _, test := range tests {
>> ++              got, err := safefilepath.FromFS(test.path)
>> ++              if (got == "") != (err != nil) {
>> ++                      t.Errorf(`FromFS(%q) = %q, %v; want "" only if err != nil`, test.path, got, err)
>> ++              }
>> ++              if got != test.result {
>> ++                      t.Errorf("FromFS(%q) = %q, %v; want %q", test.path, got, err, test.result)
>> ++              }
>> ++      }
>> ++}
>> ++
>> ++func canWriteFile(t *testing.T, name string) bool {
>> ++      path := filepath.Join(t.TempDir(), name)
>> ++      os.WriteFile(path, []byte("ok"), 0666)
>> ++      b, _ := os.ReadFile(path)
>> ++      return string(b) == "ok"
>> ++}
>> +diff --git a/src/internal/safefilepath/path_windows.go b/src/internal/safefilepath/path_windows.go
>> +new file mode 100644
>> +index 0000000..909c150
>> +--- /dev/null
>> ++++ b/src/internal/safefilepath/path_windows.go
>> +@@ -0,0 +1,95 @@
>> ++// 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.
>> ++
>> ++package safefilepath
>> ++
>> ++import (
>> ++      "syscall"
>> ++      "unicode/utf8"
>> ++)
>> ++
>> ++func fromFS(path string) (string, error) {
>> ++      if !utf8.ValidString(path) {
>> ++              return "", errInvalidPath
>> ++      }
>> ++      for len(path) > 1 && path[0] == '/' && path[1] == '/' {
>> ++              path = path[1:]
>> ++      }
>> ++      containsSlash := false
>> ++      for p := path; p != ""; {
>> ++              // Find the next path element.
>> ++              i := 0
>> ++              dot := -1
>> ++              for i < len(p) && p[i] != '/' {
>> ++                      switch p[i] {
>> ++                      case 0, '\\', ':':
>> ++                              return "", errInvalidPath
>> ++                      case '.':
>> ++                              if dot < 0 {
>> ++                                      dot = i
>> ++                              }
>> ++                      }
>> ++                      i++
>> ++              }
>> ++              part := p[:i]
>> ++              if i < len(p) {
>> ++                      containsSlash = true
>> ++                      p = p[i+1:]
>> ++              } else {
>> ++                      p = ""
>> ++              }
>> ++              // Trim the extension and look for a reserved name.
>> ++              base := part
>> ++              if dot >= 0 {
>> ++                      base = part[:dot]
>> ++              }
>> ++              if isReservedName(base) {
>> ++                      if dot < 0 {
>> ++                              return "", errInvalidPath
>> ++                      }
>> ++                      // The path element is a reserved name with an extension.
>> ++                      // Some Windows versions consider this a reserved name,
>> ++                      // while others do not. Use FullPath to see if the name is
>> ++                      // reserved.
>> ++                      if p, _ := syscall.FullPath(part); len(p) >= 4 && p[:4] == `\\.\` {
>> ++                              return "", errInvalidPath
>> ++                      }
>> ++              }
>> ++      }
>> ++      if containsSlash {
>> ++              // We can't depend on strings, so substitute \ for / manually.
>> ++              buf := []byte(path)
>> ++              for i, b := range buf {
>> ++                      if b == '/' {
>> ++                              buf[i] = '\\'
>> ++                      }
>> ++              }
>> ++              path = string(buf)
>> ++      }
>> ++      return path, nil
>> ++}
>> ++
>> ++// isReservedName reports if name is a Windows reserved device name.
>> ++// It does not detect names with an extension, which are also reserved on some Windows versions.
>> ++//
>> ++// For details, search for PRN in
>> ++// https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file.
>> ++func isReservedName(name string) bool {
>> ++      if 3 <= len(name) && len(name) <= 4 {
>> ++              switch string([]byte{toUpper(name[0]), toUpper(name[1]), toUpper(name[2])}) {
>> ++              case "CON", "PRN", "AUX", "NUL":
>> ++                      return len(name) == 3
>> ++              case "COM", "LPT":
>> ++                      return len(name) == 4 && '1' <= name[3] && name[3] <= '9'
>> ++              }
>> ++      }
>> ++      return false
>> ++}
>> ++
>> ++func toUpper(c byte) byte {
>> ++      if 'a' <= c && c <= 'z' {
>> ++              return c - ('a' - 'A')
>> ++      }
>> ++      return c
>> ++}
>> +diff --git a/src/net/http/fs.go b/src/net/http/fs.go
>> +index 57e731e..43ee4b5 100644
>> +--- a/src/net/http/fs.go
>> ++++ b/src/net/http/fs.go
>> +@@ -9,6 +9,7 @@ package http
>> + import (
>> +       "errors"
>> +       "fmt"
>> ++      "internal/safefilepath"
>> +       "io"
>> +       "io/fs"
>> +       "mime"
>> +@@ -69,14 +70,15 @@ func mapDirOpenError(originalErr error, name string) error {
>> + // Open implements FileSystem using os.Open, opening files for reading rooted
>> + // and relative to the directory d.
>> + func (d Dir) Open(name string) (File, error) {
>> +-      if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
>> +-              return nil, errors.New("http: invalid character in file path")
>> ++      path, err := safefilepath.FromFS(path.Clean("/" + name))
>> ++      if err != nil {
>> ++              return nil, errors.New("http: invalid or unsafe file path")
>> +       }
>> +       dir := string(d)
>> +       if dir == "" {
>> +               dir = "."
>> +       }
>> +-      fullName := filepath.Join(dir, filepath.FromSlash(path.Clean("/"+name)))
>> ++      fullName := filepath.Join(dir, path)
>> +       f, err := os.Open(fullName)
>> +       if err != nil {
>> +               return nil, mapDirOpenError(err, fullName)
>> +diff --git a/src/net/http/fs_test.go b/src/net/http/fs_test.go
>> +index b42ade1..941448a 100644
>> +--- a/src/net/http/fs_test.go
>> ++++ b/src/net/http/fs_test.go
>> +@@ -648,6 +648,34 @@ func TestFileServerZeroByte(t *testing.T) {
>> +       }
>> + }
>> +
>> ++func TestFileServerNamesEscape(t *testing.T) {
>> ++      t.Run("h1", func(t *testing.T) {
>> ++              testFileServerNamesEscape(t, h1Mode)
>> ++      })
>> ++      t.Run("h2", func(t *testing.T) {
>> ++              testFileServerNamesEscape(t, h2Mode)
>> ++      })
>> ++}
>> ++func testFileServerNamesEscape(t *testing.T, h2 bool) {
>> ++      defer afterTest(t)
>> ++      ts := newClientServerTest(t, h2, FileServer(Dir("testdata"))).ts
>> ++      defer ts.Close()
>> ++      for _, path := range []string{
>> ++              "/../testdata/file",
>> ++              "/NUL", // don't read from device files on Windows
>> ++      } {
>> ++              res, err := ts.Client().Get(ts.URL + path)
>> ++              if err != nil {
>> ++                      t.Fatal(err)
>> ++              }
>> ++              res.Body.Close()
>> ++              if res.StatusCode < 400 || res.StatusCode > 599 {
>> ++                      t.Errorf("Get(%q): got status %v, want 4xx or 5xx", path, res.StatusCode)
>> ++              }
>> ++
>> ++      }
>> ++}
>> ++
>> + type fakeFileInfo struct {
>> +       dir      bool
>> +       basename string
>> +diff --git a/src/os/file.go b/src/os/file.go
>> +index e717f17..cb87158 100644
>> +--- a/src/os/file.go
>> ++++ b/src/os/file.go
>> +@@ -37,12 +37,12 @@
>> + // Note: The maximum number of concurrent operations on a File may be limited by
>> + // the OS or the system. The number should be high, but exceeding it may degrade
>> + // performance or cause other issues.
>> +-//
>> + package os
>> +
>> + import (
>> +       "errors"
>> +       "internal/poll"
>> ++      "internal/safefilepath"
>> +       "internal/testlog"
>> +       "internal/unsafeheader"
>> +       "io"
>> +@@ -623,6 +623,8 @@ func isWindowsNulName(name string) bool {
>> + // the /prefix tree, then using DirFS does not stop the access any more than using
>> + // os.Open does. DirFS is therefore not a general substitute for a chroot-style security
>> + // mechanism when the directory tree contains arbitrary content.
>> ++//
>> ++// The directory dir must not be "".
>> + func DirFS(dir string) fs.FS {
>> +       return dirFS(dir)
>> + }
>> +@@ -641,10 +643,11 @@ func containsAny(s, chars string) bool {
>> + type dirFS string
>> +
>> + func (dir dirFS) Open(name string) (fs.File, error) {
>> +-      if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) {
>> +-              return nil, &PathError{Op: "open", Path: name, Err: ErrInvalid}
>> ++      fullname, err := dir.join(name)
>> ++      if err != nil {
>> ++              return nil, &PathError{Op: "stat", Path: name, Err: err}
>> +       }
>> +-      f, err := Open(string(dir) + "/" + name)
>> ++      f, err := Open(fullname)
>> +       if err != nil {
>> +               return nil, err // nil fs.File
>> +       }
>> +@@ -652,16 +655,35 @@ func (dir dirFS) Open(name string) (fs.File, error) {
>> + }
>> +
>> + func (dir dirFS) Stat(name string) (fs.FileInfo, error) {
>> +-      if !fs.ValidPath(name) || runtime.GOOS == "windows" && containsAny(name, `\:`) {
>> +-              return nil, &PathError{Op: "stat", Path: name, Err: ErrInvalid}
>> ++      fullname, err := dir.join(name)
>> ++      if err != nil {
>> ++              return nil, &PathError{Op: "stat", Path: name, Err: err}
>> +       }
>> +-      f, err := Stat(string(dir) + "/" + name)
>> ++      f, err := Stat(fullname)
>> +       if err != nil {
>> +               return nil, err
>> +       }
>> +       return f, nil
>> + }
>> +
>> ++// join returns the path for name in dir.
>> ++func (dir dirFS) join(name string) (string, error) {
>> ++      if dir == "" {
>> ++              return "", errors.New("os: DirFS with empty root")
>> ++      }
>> ++      if !fs.ValidPath(name) {
>> ++              return "", ErrInvalid
>> ++      }
>> ++      name, err := safefilepath.FromFS(name)
>> ++      if err != nil {
>> ++              return "", ErrInvalid
>> ++      }
>> ++      if IsPathSeparator(dir[len(dir)-1]) {
>> ++              return string(dir) + name, nil
>> ++      }
>> ++      return string(dir) + string(PathSeparator) + name, nil
>> ++}
>> ++
>> + // ReadFile reads the named file and returns the contents.
>> + // A successful call returns err == nil, not err == EOF.
>> + // Because ReadFile reads the whole file, it does not treat an EOF from Read
>> +diff --git a/src/os/os_test.go b/src/os/os_test.go
>> +index 506f1fb..be269bb 100644
>> +--- a/src/os/os_test.go
>> ++++ b/src/os/os_test.go
>> +@@ -2702,6 +2702,44 @@ func TestDirFS(t *testing.T) {
>> +       if err == nil {
>> +               t.Fatalf(`Open testdata\dirfs succeeded`)
>> +       }
>> ++
>> ++      // Test that Open does not open Windows device files.
>> ++      _, err = d.Open(`NUL`)
>> ++      if err == nil {
>> ++              t.Errorf(`Open NUL succeeded`)
>> ++      }
>> ++}
>> ++
>> ++func TestDirFSRootDir(t *testing.T) {
>> ++      cwd, err := os.Getwd()
>> ++      if err != nil {
>> ++              t.Fatal(err)
>> ++      }
>> ++      cwd = cwd[len(filepath.VolumeName(cwd)):] // trim volume prefix (C:) on Windows
>> ++      cwd = filepath.ToSlash(cwd)               // convert \ to /
>> ++      cwd = strings.TrimPrefix(cwd, "/")        // trim leading /
>> ++
>> ++      // Test that Open can open a path starting at /.
>> ++      d := DirFS("/")
>> ++      f, err := d.Open(cwd + "/testdata/dirfs/a")
>> ++      if err != nil {
>> ++              t.Fatal(err)
>> ++      }
>> ++      f.Close()
>> ++}
>> ++
>> ++func TestDirFSEmptyDir(t *testing.T) {
>> ++      d := DirFS("")
>> ++      cwd, _ := os.Getwd()
>> ++      for _, path := range []string{
>> ++              "testdata/dirfs/a",                          // not DirFS(".")
>> ++              filepath.ToSlash(cwd) + "/testdata/dirfs/a", // not DirFS("/")
>> ++      } {
>> ++              _, err := d.Open(path)
>> ++              if err == nil {
>> ++                      t.Fatalf(`DirFS("").Open(%q) succeeded`, path)
>> ++              }
>> ++      }
>> + }
>> +
>> + func TestDirFSPathsValid(t *testing.T) {
>> --
>> 2.40.0
>>
>>
>> -=-=-=-=-=-=-=-=-=-=-=-
>> Links: You receive all messages sent to this group.
>> View/Reply Online (#178900): https://lists.openembedded.org/g/openembedded-core/message/178900
>> Mute This Topic: https://lists.openembedded.org/mt/97763091/3620601
>> Group Owner: openembedded-core+owner@lists.openembedded.org
>> Unsubscribe: https://lists.openembedded.org/g/openembedded-core/unsub [steve@sakoman.com]
>> -=-=-=-=-=-=-=-=-=-=-=-
>>


      reply	other threads:[~2023-03-27 22:26 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-03-21 19:36 [kirkstone][PATCH v2] go: fix CVE-2022-2879 and CVE-2022-41720 Sakib Sajal
2023-03-22 16:21 ` [OE-core] " Steve Sakoman
2023-03-27 22:26   ` Sakib Sajal [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=c4fcf1ab-3cf5-03f3-9fc2-36ee9f243634@windriver.com \
    --to=sakib.sajal@windriver.com \
    --cc=openembedded-core@lists.openembedded.org \
    --cc=steve@sakoman.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox