Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

archive/zip: Reader.Open panics on empty string #48085

Closed
colin-sitehost opened this issue Aug 31, 2021 · 32 comments
Closed

archive/zip: Reader.Open panics on empty string #48085

colin-sitehost opened this issue Aug 31, 2021 · 32 comments
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done. Security
Milestone

Comments

@colin-sitehost
Copy link

colin-sitehost commented Aug 31, 2021

What version of Go are you using (go version)?

$ go version
go version go1.17 linux/amd64

Does this issue reproduce with the latest release?

What operating system and processor architecture are you using (go env)?

go env Output
$ go env
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/user/.cache/go-build"
GOENV="/home/user/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/user/go/pkg/mod"
GOOS="linux"
GOPATH="/home/colin/go"
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/lib/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.17"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build2958766942=/tmp/go-build -gno-record-gcc-switches"

What did you do?

r, _ := zip.NewReader(bytes.NewReader(raw), int64(len(raw)))
r.Open("")

https://play.golang.org/p/7x5dvzJSoCY

What did you expect to see?

What did you see instead?

panic: runtime error: index out of range [-1]

goroutine 1 [running]:
archive/zip.split(...)
	/usr/local/go-faketime/src/archive/zip/reader.go:800
archive/zip.(*Reader).openLookup(0x0, {0x0, 0xffffffffffffffff})
	/usr/local/go-faketime/src/archive/zip/reader.go:821 +0x265
archive/zip.(*Reader).Open(0x4e1360, {0x0, 0x16})
	/usr/local/go-faketime/src/archive/zip/reader.go:785 +0x45
main.main()
	/tmp/sandbox521722687/prog.go:11 +0xb2

Program exited: status 2.

Anything else?

This was discovered via io/fs.WalkDir, but the root cause is Reader.Open: https://play.golang.org/p/WWHxZRKu2gY:

panic: runtime error: index out of range [-1]

goroutine 1 [running]:
archive/zip.split(...)
	/usr/local/go-faketime/src/archive/zip/reader.go:800
archive/zip.(*Reader).openLookup(0x40c6a7, {0x0, 0x4bb740})
	/usr/local/go-faketime/src/archive/zip/reader.go:821 +0x265
archive/zip.(*Reader).Open(0x40c354, {0x0, 0xc000134000})
	/usr/local/go-faketime/src/archive/zip/reader.go:785 +0x45
io/fs.Stat({0x4e4060, 0xc000134000}, {0x0, 0x0})
	/usr/local/go-faketime/src/io/fs/stat.go:25 +0x9c
io/fs.WalkDir({0x4e4060, 0xc000134000}, {0x0, 0x0}, 0x0)
	/usr/local/go-faketime/src/io/fs/walk.go:108 +0x3c
main.main()
	/tmp/sandbox3566573894/prog.go:12 +0xbe

Program exited: status 2.

I was not expecting this to work, since most other fs.FS implementations (say embed.FS) return a not found error when root == "", but a panic seemed like a bug.

@mengzhuo
Copy link
Contributor

if name[len(name)-1] == '/' {
isDir = true
name = name[:len(name)-1]
}
i := len(name) - 1

It seems to be a bug, I'll try to fix it.

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/346349 mentions this issue: archive/zip: do path valid check first in Open

@colin-sitehost
Copy link
Author

colin-sitehost commented Aug 31, 2021

does this qualify for backporting to the current releases? (happy to assist with that effort)

@mengzhuo
Copy link
Contributor

mengzhuo commented Sep 1, 2021

cc @dsnet @rolandshoemaker

@cherrymui cherrymui added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Sep 2, 2021
@cherrymui cherrymui added this to the Backlog milestone Sep 2, 2021
@Strum355
Copy link

Strum355 commented Sep 3, 2021

Also having the same issue/symptoms, code and zip (actually JAR) file attached. Of note when printing the file names from reader.Files (as commented out below) is that there are absolute paths including a /. See picture at end

Details
package main

import (
	"archive/zip"
	"io"
	"os"
	"path"
)

const destination = "./extracted"

func main() {
	reader, err := zip.OpenReader("liquibase-core-4.4.3-sources.zip")
	if err != nil {
		panic(err)
	}
	defer reader.Close()

	// names := []string{}

	// for _, file := range reader.File {
	// 	names = append(names, "'"+file.Name+"'")
	// }

	// fmt.Println(strings.Join(names, "\n"))

	for _, file := range reader.File {
		outputPath := path.Join(destination, file.Name)
		err := copyZipFileEntry(reader, file, outputPath)
		if err != nil {
			panic(err)
		}
	}
}

func copyZipFileEntry(reader *zip.ReadCloser, entry *zip.File, outputPath string) (err error) {
	inputFile, err := reader.Open(entry.Name)
	if err != nil {
		return err
	}
	defer func() {
		err1 := inputFile.Close()
		if err == nil {
			err = err1
		}
	}()

	if err = os.MkdirAll(path.Dir(outputPath), 0700); err != nil {
		return err
	}
	outputFile, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
	if err != nil {
		return err
	}
	defer func() {
		err1 := outputFile.Close()
		if err == nil {
			err = err1
		}
	}()

	_, err = io.Copy(outputFile, inputFile)
	return err
}

liquibase-core-4.4.3-sources.zip

image

@Strum355
Copy link

Strum355 commented Sep 3, 2021

Our stacktrace is slightly different, but ultimately still hits the panic in archive/zip.split. The change listed above would not address this for us, as we are trying to open a legitimate file META-INF/MANIFEST.MF and hitting the panic on the first (and only) call to initFileList

panic: runtime error: index out of range [-1]

goroutine 1 [running]:
archive/zip.split(...)
        /usr/lib/go/src/archive/zip/reader.go:800
archive/zip.fileEntryLess({0xc000168900, 0x4bacc0}, {0x569758, 0xc00021e000})
        /usr/lib/go/src/archive/zip/reader.go:774 +0x1e5
archive/zip.(*Reader).initFileList.func1.1(0xc00007ca88, 0x493)
        /usr/lib/go/src/archive/zip/reader.go:768 +0x4f
sort.doPivot_func({0xc00007ca88, 0xc0001a92f0}, 0x0, 0x4e0)
        /usr/lib/go/src/sort/zfuncversion.go:87 +0x1d9
sort.quickSort_func({0xc00007ca88, 0xc0001a92f0}, 0xc00000c060, 0x0, 0xc00007cac8)
        /usr/lib/go/src/sort/zfuncversion.go:143 +0x85
sort.Slice({0x4afa00, 0xc00000c060}, 0x500)
        /usr/lib/go/src/sort/slice.go:20 +0x9f
archive/zip.(*Reader).initFileList.func1()
        /usr/lib/go/src/archive/zip/reader.go:768 +0x42a
sync.(*Once).doSlow(0x0, 0x0)
        /usr/lib/go/src/sync/once.go:68 +0xd2
sync.(*Once).Do(...)
        /usr/lib/go/src/sync/once.go:59
archive/zip.(*Reader).initFileList(0x0)
        /usr/lib/go/src/archive/zip/reader.go:738 +0x45
archive/zip.(*Reader).Open(0x462b3e, {0xc000020228, 0xc00007cdc8})
        /usr/lib/go/src/archive/zip/reader.go:783 +0x2c
main.copyZipFileEntry(0x4e7e00, 0xc00000e018, {0xc0001b0dc0, 0x1e})
        /home/noah/Sourcegraph/coursiertest/main.go:40 +0x65
main.main()
        /home/noah/Sourcegraph/coursiertest/main.go:32 +0x377
exit status 2

@Strum355
Copy link

Strum355 commented Sep 3, 2021

Screenshot showing debugger state when calling initFileList. Note the filename / being transformed to empty string. Simply doing a check after toValidName for empty string and doing continue results in panic: open /: file does not exist later in program execution, which while could be ignored, doesn't seem like a proper solution imo but also not sure what the behaviour should be in that case.
image

@colin-sitehost
Copy link
Author

colin-sitehost commented Sep 5, 2021

per the spec, what you provided is an invalid zip file:

   4.4.17.1 The name of the file, with optional relative path.
  The path stored MUST NOT contain a drive or
  device letter, or a leading slash.  All slashes
  MUST be forward slashes '/' as opposed to
  backwards slashes '\' for compatibility with Amiga
  and UNIX file systems etc.  If input came from standard
  input, there is no file name field.  

As such, I think it is totally valid for go to reject archives with input path names that are absolute (either /path/to/ or C:/path/to); that being said, for compatibility, we could chose to translate them.


it looks like Info-ZIP helps out during unzip:

warning:  stripped absolute path spec from /var/jenkins/workspace/liquibasePro/4.4.x/53/liquibase/liquibase-dist/target/archive/licenses/epl-1.0.txt
  inflating: var/jenkins/workspace/liquibasePro/4.4.x/53/liquibase/liquibase-dist/target/archive/licenses/epl-1.0.txt
warning:  stripped absolute path spec from /var/jenkins/workspace/liquibasePro/4.4.x/53/liquibase/liquibase-dist/target/archive/licenses/isc.txt
  inflating: var/jenkins/workspace/liquibasePro/4.4.x/53/liquibase/liquibase-dist/target/archive/licenses/isc.txt
warning:  stripped absolute path spec from /var/jenkins/workspace/liquibasePro/4.4.x/53/liquibase/liquibase-dist/target/archive/licenses/lgpl-3.0.txt
  inflating: var/jenkins/workspace/liquibasePro/4.4.x/53/liquibase/liquibase-dist/target/archive/licenses/lgpl-3.0.txt

but it refuses to create one:

[user@localhost ~]$  zip info /path/to/file
[user@localhost ~]$ unzip -l info.zip
Archive:  info.zip
Length      Date    Time    Name
---------  ---------- -----   ----
   2075  2021-09-02 22:41   path/to/file
---------                     -------
   2075                     1 file

@Strum355
Copy link

Strum355 commented Sep 5, 2021

As such, I think it is totally valid for go to panic on input path names that are absolute

I very strongly disagree with this. This was a cause of panics in production for us, and as a result, regardless of what the spec says, I strongly believe that archive/zip should not panic in such a scenario, as absolute paths in zips can and do exist in real-world files. If anything, I would consider this issue almost as serious as two previous panics in archive/zip, both of which have a CVE (first and second). Crafted/invalid input should not cause a panic, it should be handled appropriately and defensively.

@colin-sitehost
Copy link
Author

colin-sitehost commented Sep 5, 2021

I agree that this panic seems accidental, and we could probably return an error on NewReader, (I have reworded my previous comment to reflect this reality); but it is pretty common for stdlib to panic in absurd conditions (index out of bound, nil pointer, etc.), especially when it makes for a cleaner interface.

@Strum355
Copy link

Strum355 commented Sep 5, 2021

but it is pretty common for stdlib to panic in absurd conditions (index out of bound, nil pointer, etc.), especially when it makes for a cleaner interface.

I don't believe that this case is comparable, nor that the scenario is particularly "absurd" when real world files exist with this characteristic. There is a precedent from the above linked CVEs (most particularly the second one) that the panic from absolute paths should be addressed, as well as a precedent from other utilities that handle this particular case.

I completely understand that the case wouldn't have been thought of while the code was written, I lay no blame on anyone for that, Im simply hoping for the scenario to be handled in such a way that it is still possible for me to consume the zip file and do with the erroneous paths as I wish (in this case, we would skip extracting them).

Of additional note, the issue is not triggered when I do *archive/zip.File.Open, but it is triggered when I do *archive/zip.Reader.Open, so theres a definitive inconsistency there that should be addressed. Also do observe that the panic is triggered not when I try to access an absolute path file, but when I was trying to access META-INF/MANIFEST.MF.

Regardless of our thoughts, I was strongly recommended to notify the security team of this issue, which I have 🙂 I appreciate your thoughts nonetheless

@FiloSottile
Copy link
Contributor

Thank you for reporting this to security@golang.org. Invalid input should not cause programs to panic, if the input could be attacker controlled. If this required a call to Open("") to trigger, it could have been borderline, since it's hard for an attacker to control the argument to Open. However, the reproducer in #48085 (comment) triggers a panic with a real file name.

package main

import "archive/zip"

func main() {
	reader, err := zip.OpenReader("liquibase-core-4.4.3-sources.zip")
	if err != nil {
		panic(err)
	}

	reader.Open("META-INF/MANIFEST.MF")
}

We'll backport this as a security fix in the PUBLIC track. @gopherbot, please open backport issues.

@gopherbot
Copy link
Contributor

Backport issue(s) opened: #48251 (for 1.16), #48252 (for 1.17).

Remember to create the cherry-pick CL(s) as soon as the patch is submitted to master, according to https://golang.org/wiki/MinorReleases.

@Jason7602
Copy link
Contributor

I found that the key reason for this problem was the function toValidName which coerces name to be a vaild name for fs.FS.Open will cause the result of vaild name to "" when the file.Name is one of the following:

/
//
\
../

In fact, attacker can easily construct such names. And the example liquibase-core-4.4.3-sources.zip is one of the above case which including the / in one of the file.Name .
I have constructed a zip binary file that can contain the above cases at the same time which can easily cause panic.

func TestIssue48085(t *testing.T) {
	data := []byte{
		0x50, 0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x08,
		0x00, 0x00, 0x06, 0x94, 0x05, 0x53, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2f, 0x50,
		0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x78, 0x67, 0x2e, 0x53, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x02, 0x00, 0x00, 0x00, 0x2f, 0x2f, 0x50,
		0x4b, 0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x78, 0x67, 0x2e, 0x53, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x01, 0x00, 0x00, 0x00, 0x5c, 0x50, 0x4b,
		0x03, 0x04, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x91, 0x68, 0x2e, 0x53, 0x85, 0x11, 0x4a, 0x0d,
		0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
		0x09, 0x00, 0x00, 0x00, 0x2f, 0x74, 0x65, 0x73,
		0x74, 0x2e, 0x74, 0x78, 0x74, 0x68, 0x65, 0x6c,
		0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64,
		0x50, 0x4b, 0x01, 0x02, 0x14, 0x03, 0x0a, 0x00,
		0x00, 0x08, 0x00, 0x00, 0x06, 0x94, 0x05, 0x53,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
		0xed, 0x41, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x50,
		0x4b, 0x01, 0x02, 0x3f, 0x00, 0x0a, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x78, 0x67, 0x2e, 0x53, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x02, 0x00, 0x24, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
		0x00, 0x1f, 0x00, 0x00, 0x00, 0x2f, 0x2f, 0x0a,
		0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
		0x00, 0x18, 0x00, 0x93, 0x98, 0x25, 0x57, 0x25,
		0xa9, 0xd7, 0x01, 0x93, 0x98, 0x25, 0x57, 0x25,
		0xa9, 0xd7, 0x01, 0x93, 0x98, 0x25, 0x57, 0x25,
		0xa9, 0xd7, 0x01, 0x50, 0x4b, 0x01, 0x02, 0x3f,
		0x00, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78,
		0x67, 0x2e, 0x53, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
		0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x20, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00,
		0x00, 0x5c, 0x0a, 0x00, 0x20, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x93, 0x98,
		0x25, 0x57, 0x25, 0xa9, 0xd7, 0x01, 0x93, 0x98,
		0x25, 0x57, 0x25, 0xa9, 0xd7, 0x01, 0x93, 0x98,
		0x25, 0x57, 0x25, 0xa9, 0xd7, 0x01, 0x50, 0x4b,
		0x01, 0x02, 0x3f, 0x00, 0x0a, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x91, 0x68, 0x2e, 0x53, 0x85, 0x11,
		0x4a, 0x0d, 0x0b, 0x00, 0x00, 0x00, 0x0b, 0x00,
		0x00, 0x00, 0x09, 0x00, 0x24, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
		0x5e, 0x00, 0x00, 0x00, 0x2f, 0x74, 0x65, 0x73,
		0x74, 0x2e, 0x74, 0x78, 0x74, 0x0a, 0x00, 0x20,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18,
		0x00, 0xa9, 0x80, 0x51, 0x01, 0x26, 0xa9, 0xd7,
		0x01, 0x31, 0xd1, 0x57, 0x01, 0x26, 0xa9, 0xd7,
		0x01, 0xdf, 0x48, 0x85, 0xf9, 0x25, 0xa9, 0xd7,
		0x01, 0x50, 0x4b, 0x05, 0x06, 0x00, 0x00, 0x00,
		0x00, 0x04, 0x00, 0x04, 0x00, 0x31, 0x01, 0x00,
		0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00,
	}
	r, err := NewReader(bytes.NewReader([]byte(data)), int64(len(data)))
	if err != nil {
		t.Fatalf("Error reading the archive: %v", err)
	}
	f, err := r.Open("")
	if f != nil || err == nil || !strings.Contains(err.Error(), "invalid argument") {
		t.Fatalf("expecting open failed with invalid argument, got:%s %q", err, f)
	}
	_, err = r.Open("test.txt")
	if err != nil {
		t.Fatalf("expecting open failed with invalid argument, got:%s", err)
	}
}

@mengzhuo submited the CL346349 solved the bug of (r *Reader) Open when the opened name is "", but do not solved the oboved cases.
So to totally fix this bug, we can add a judgment after the toValidName function, if the name is "", just continue directly.

name := toValidName(file.Name)
if name == "" {
	continue
}

because when the name is "", the processing is meaningless and does not need to be added to the fileList. Instead, the attacker is given an opportunity.
@colin-sitehost @FiloSottile @mengzhuo what do you think of this fix solution?
if there is no problem, I will try to fix this bug.

@mengzhuo
Copy link
Contributor

Ha, good catch, I will abandon my CL. Thanks for the info.

@Strum355
Copy link

@Jason7602 I believe I had tried that already and it did not fully solve the issue; iterating over reader.Files still surfaces the / entry, which results in an error of that file not being found when trying to open it. That may be a good enough of a solution however, just feels a bit unusual to me to have two differing sources of what file entries exist. I can double check what the actual result of that change was however to make sure Im not wrong in this statement.

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/349770 mentions this issue: archive/zip: check especial name in zip file to prevent panicinng

@Jason7602
Copy link
Contributor

Jason7602 commented Sep 15, 2021

@Jason7602 I believe I had tried that already and it did not fully solve the issue; iterating over reader.Files still surfaces the / entry, which results in an error of that file not being found when trying to open it. That may be a good enough of a solution however, just feels a bit unusual to me to have two differing sources of what file entries exist. I can double check what the actual result of that change was however to make sure Im not wrong in this statement.

Actually, my CL349770 has added check both in *Reader.Open and initFileList, which should have solved the problem completely, but I still don't understand the case that you described. If possible, please share your test code, or use the CL349770 to see if your problem is completely solved.

@Jason7602
Copy link
Contributor

ping @dsnet @bradfitz

@Strum355
Copy link

@Jason7602 I believe I had tried that already and it did not fully solve the issue; iterating over reader.Files still surfaces the / entry, which results in an error of that file not being found when trying to open it. That may be a good enough of a solution however, just feels a bit unusual to me to have two differing sources of what file entries exist. I can double check what the actual result of that change was however to make sure Im not wrong in this statement.

Actually, my CL349770 has added check both in *Reader.Open and initFileList, which should have solved the problem completely, but I still don't understand the case that you described. If possible, please share your test code, or use the CL349770 to see if your problem is completely solved.

Just so I know we're on the same page, when iterating over archive/zip.(*ReadCloser).File slice, and/or when directly (or via iteration) trying to read an archive/zip.(*File) via archive/zip.(*ReadCloser).Open that corresponds with an absolute path entry, is the intended behaviour to return an fs.PathError{fs.ErrInvalid}, even though the file actually exists inside the zip archive?

If that's the case, should archive/zip.(*File).Open also return fs.PathError{fs.ErrInvalid} for consistency? Would feel like a confusing inconsistency if not.

At the same time I'm not sure if we should be erroring on accessing "legitimate" files. "But the files are there! Why am I getting 'invalid argument' from Go but not from e.g. unzip!"

@Jason7602
Copy link
Contributor

Jason7602 commented Sep 17, 2021

When the zip file exist absolute path entry, in function initFileList, the toValidName will remove / or ../ element in front of the path and change it to the relative path entry and add it to the reader.fileList, So even we do not use fs.ValidPathto check the path when via archive/zip.(*ReadCloser).Open that corresponds with an absolute path entry, it also will return fs.ErrNotExist. Actually the files are in the zip file, but Go return the result of file not exist which become more confused.

Actually the comment of archive/zip.(*ReadCloser).Open clearly tells developers not to open the filename leading with / or ../
elements.

// Open opens the named file in the ZIP archive,
// using the semantics of fs.FS.Open:
// paths are always slash separated, with no
// leading / or ../ elements.
func (r *Reader) Open(name string) (fs.File, error) {

So to better distinguish the two error cases, return the fs.PathError{fs.ErrInvalid} when open the filename leading with / or ../ is more accurate from my point.

@Jason7602
Copy link
Contributor

ping @dsnet @bradfitz

@heschi
Copy link
Contributor

heschi commented Oct 6, 2021

Ping from the release team -- this is listed as a potential cherrypick but hasn't moved in a while. Any updates?

@Jason7602
Copy link
Contributor

Actually, this is a very serious security bug of archive/zip package. Attacker can easily construct such zip file and cause the server to crash. This issue is very important to our product, cause our products use the zip library extensively. I hope the release team can review the CL349770 as soon as possible.
Personally, I think this should be a CVE.
Ping @dsnet @bradfitz

@mvdan
Copy link
Member

mvdan commented Oct 14, 2021

Note that neither Joe nor Brad work at Google anymore, so while they're the right owners to ping, in terms of security you probably want @golang/security.

@dsnet
Copy link
Member

dsnet commented Oct 14, 2021

I've completely context switched any knowledge of archive/zip out of my mind. It'll take me time to look at this unless someone from @golang/security beats me to it. As @mvdan mentioned, I'm no longer at Google, so it's not my day job to work on Go and investigate issues like this.

@FiloSottile
Copy link
Contributor

No worries, we'll take this and provide a fix in advance of the next minor release.

@Jason7602
Copy link
Contributor

No worries, we'll take this and provide a fix in advance of the next minor release.

Actually, I had uploaded a CL349770 that might fix this issue, could you take a look?

@katiehockman katiehockman added Security NeedsFix The path to resolution is known, but the work has not been done. and removed NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. labels Oct 28, 2021
@FiloSottile
Copy link
Contributor

@colin-sitehost would you like to be credited in the release announcement for reporting this? If so, how/with what name/affiliation?

@colin-sitehost
Copy link
Author

yeah, happy for myself and my employer to be credited: Colin Arnott, SiteHost; let me know if odher formatting questions come up

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/360859 mentions this issue: [release-branch.go1.17] archive/zip: don't panic on (*Reader).Open

@gopherbot
Copy link
Contributor

Change https://golang.org/cl/360858 mentions this issue: [release-branch.go1.16] archive/zip: don't panic on (*Reader).Open

gopherbot pushed a commit that referenced this issue Nov 3, 2021
Previously, opening a zip with (*Reader).Open could result in a panic if
the zip contained a file whose name was exclusively made up of slash
characters or ".." path elements.

Open could also panic if passed the empty string directly as an argument.

Now, any files in the zip whose name could not be made valid for
fs.FS.Open will be skipped, and no longer added to the fs.FS file list,
although they are still accessible through (*Reader).File.

Note that it was already the case that a file could be accessible from
(*Reader).Open with a name different from the one in (*Reader).File, as
the former is the cleaned name, while the latter is the original one.

Finally, made the actual panic site robust as a defense-in-depth measure.

Fixes CVE-2021-41772
Fixes #48252
Updates #48085

Co-authored-by: Filippo Valsorda <filippo@golang.org>
Change-Id: I6271a3f2892e7746f52e213b8eba9a1bba974678
Reviewed-on: https://go-review.googlesource.com/c/go/+/349770
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Trust: Katie Hockman <katie@golang.org>
Trust: Julie Qiu <julie@golang.org>
(cherry picked from commit b246873)
Reviewed-on: https://go-review.googlesource.com/c/go/+/360859
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
gopherbot pushed a commit that referenced this issue Nov 3, 2021
Previously, opening a zip with (*Reader).Open could result in a panic if
the zip contained a file whose name was exclusively made up of slash
characters or ".." path elements.

Open could also panic if passed the empty string directly as an argument.

Now, any files in the zip whose name could not be made valid for
fs.FS.Open will be skipped, and no longer added to the fs.FS file list,
although they are still accessible through (*Reader).File.

Note that it was already the case that a file could be accessible from
(*Reader).Open with a name different from the one in (*Reader).File, as
the former is the cleaned name, while the latter is the original one.

Finally, made the actual panic site robust as a defense-in-depth measure.

Fixes CVE-2021-41772
Fixes #48251
Updates #48085

Co-authored-by: Filippo Valsorda <filippo@golang.org>
Change-Id: I6271a3f2892e7746f52e213b8eba9a1bba974678
Reviewed-on: https://go-review.googlesource.com/c/go/+/349770
Run-TryBot: Filippo Valsorda <filippo@golang.org>
Reviewed-by: Katie Hockman <katie@golang.org>
Reviewed-by: Filippo Valsorda <filippo@golang.org>
Trust: Katie Hockman <katie@golang.org>
Trust: Julie Qiu <julie@golang.org>
(cherry picked from commit b246873)
Reviewed-on: https://go-review.googlesource.com/c/go/+/360858
Trust: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
@golang golang locked and limited conversation to collaborators Nov 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsFix The path to resolution is known, but the work has not been done. Security
Projects
None yet
Development

No branches or pull requests