CVE-2020-7667 (published 2020-06-24T12:15Z)

In package github.com/sassoftware/go-rpmutils/cpio before version 0.1.0, the CPIO extraction functionality doesn\'t sanitize the paths of the archived files for leading and non-leading ".." which leads in file extraction outside of the current directory. Note: the fixing commit was applied to all affected versions which were re-released.

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/sassoftware/go-rpmutils
commit_details: repo_url: https://github.com/sassoftware/go-rpmutils
author: Michael Tharp
author_date: 2020-06-12 11:15:04-04:00
msg: Prevent cpio.Extract from writing into parent directories
repository: repo_url: https://github.com/sassoftware/go-rpmutils
repo_name: sassoftware/go-rpmutils
description: Golang implementation of parsing RPM packages
repo_language: Go
owner: sassoftware
file_changes: filename: extract.go
change_type: ModificationType.MODIFY
diff: @@ -21,6 +21,8 @@ import (
 	"io"
 	"os"
 	"path"
+	"path/filepath"
+	"strings"
 
 	"github.com/sassoftware/go-rpmutils/fileutil"
 )
@@ -39,7 +41,9 @@ const (
 	S_ISSOCK = 0140000 // Socket
 )
 
+// Extract the contents of a cpio stream from r to the destination directory dest
 func Extract(rs io.Reader, dest string) error {
+	dest = filepath.Clean(filepath.FromSlash(dest))
 	linkMap := make(map[int][]string)
 
 	stream := NewCpioStream(rs)
@@ -54,16 +58,21 @@ func Extract(rs io.Reader, dest string) error {
 			break
 		}
 
-		target := path.Join(dest, path.Clean(entry.Header.filename))
-		parent := path.Dir(target)
-
+		// sanitize path
+		target := path.Clean(entry.Header.filename)
+		for strings.HasPrefix(target, "../") {
+			target = target[3:]
+		}
+		target = filepath.Join(dest, filepath.FromSlash(target))
+		if !strings.HasPrefix(target, dest+string(filepath.Separator)) && dest != target {
+			// this shouldn't happen due to the sanitization above but always check
+			return fmt.Errorf("invalid cpio path %q", entry.Header.filename)
+		}
 		// Create the parent directory if it doesn't exist.
-		if _, err := os.Stat(parent); os.IsNotExist(err) {
-			if err := os.MkdirAll(parent, 0755); err != nil {
-				return err
-			}
+		parent := filepath.Dir(target)
+		if err := os.MkdirAll(parent, 0755); err != nil {
+			return err
 		}
-
 		// FIXME: Need a makedev implementation in go.
 
 		switch entry.Header.Mode() &^ 07777 {

programming_language: Go
filename: extract_test.go
change_type: ModificationType.MODIFY
diff: @@ -19,6 +19,7 @@ package cpio
 import (
 	"io/ioutil"
 	"os"
+	"path/filepath"
 	"testing"
 	"testing/iotest"
 
@@ -58,6 +59,27 @@ func TestExtract(t *testing.T) {
 	}
 }
 
+func TestExtractDotdot(t *testing.T) {
+	setupLogging()
+	f, err := os.Open("../testdata/dotdot.cpio")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer f.Close()
+	tmpdir, err := ioutil.TempDir("", "cpio")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(tmpdir)
+	err = Extract(f, tmpdir)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if _, err := os.Stat(filepath.Join(tmpdir, "aaaaaaaaa")); err != nil {
+		t.Error("expected file with ../ to extract into top of destdir:", err)
+	}
+}
+
 var _format = logging.MustStringFormatter(
 	`%{color}%{time:15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
 )

programming_language: Go
filename: rpmutils_test.go
change_type: ModificationType.MODIFY
diff: @@ -28,6 +28,7 @@ func TestReadHeader(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	defer f.Close()
 
 	hdr, err := ReadHeader(iotest.HalfReader(f))
 	if err != nil {
@@ -62,6 +63,7 @@ func TestPayloadReader(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	defer f.Close()
 
 	rpm, err := ReadRpm(iotest.HalfReader(f))
 	if err != nil {
@@ -92,6 +94,7 @@ func TestExpandPayload(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	defer f.Close()
 
 	rpm, err := ReadRpm(iotest.HalfReader(f))
 	if err != nil {
@@ -102,6 +105,7 @@ func TestExpandPayload(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
+	defer os.RemoveAll(tmpdir)
 
 	if err := rpm.ExpandPayload(tmpdir); err != nil {
 		t.Fatal(err)

programming_language: Go