Skip to content

Commit 465e820

Browse files
Path traversal changes
1 parent 20d957e commit 465e820

2 files changed

Lines changed: 378 additions & 22 deletions

File tree

internal/services/osinstaller/linux-mac-utils.go

Lines changed: 56 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,64 @@ import (
77
"compress/gzip"
88
"fmt"
99
"io"
10-
"io/fs"
1110
"log"
1211
"os"
1312
"os/exec"
1413
"path/filepath"
14+
"strings"
1515
"syscall"
1616

1717
"github.com/checkmarx/ast-cli/internal/logger"
1818
)
1919

20-
const dirDefault int = 0755
20+
// dirDefault is the permission bits applied to directories created during extraction.
21+
const dirDefault os.FileMode = 0755
22+
23+
// maxExtractBytes caps how many bytes a single tar entry may expand to,
24+
// preventing decompression-bomb (tar-bomb) attacks.
25+
const maxExtractBytes int64 = 500 * 1024 * 1024 // 500 MB
2126

2227
// UnzipOrExtractFiles Extracts all the files from the tar.gz file
2328
func UnzipOrExtractFiles(installationConfiguration *InstallationConfiguration) error {
2429
logger.PrintIfVerbose("Extracting files in: " + installationConfiguration.WorkingDir())
2530
filePath := filepath.Join(installationConfiguration.WorkingDir(), installationConfiguration.FileName)
31+
2632
gzipStream, err := os.Open(filePath)
2733
if err != nil {
2834
fmt.Println("error when open file ", filePath, err)
2935
return err
3036
}
37+
defer gzipStream.Close()
38+
3139
uncompressedStream, err := gzip.NewReader(gzipStream)
3240
if err != nil {
3341
log.Println("ExtractTarGz: NewReader failed ", err)
3442
return err
3543
}
44+
defer uncompressedStream.Close()
3645

37-
tarReader := tar.NewReader(uncompressedStream)
46+
return extractFiles(installationConfiguration, tar.NewReader(uncompressedStream))
47+
}
3848

39-
err = extractFiles(installationConfiguration, tarReader)
40-
if err != nil {
41-
return err
49+
// safeJoin validates that name is a relative path and that the resolved
50+
// destination stays inside workingDir, preventing path traversal attacks.
51+
func safeJoin(workingDir, name string) (string, error) {
52+
if name == "" || name == "." {
53+
return "", fmt.Errorf("illegal file path (empty or dot): %s", name)
4254
}
43-
return nil
55+
if filepath.IsAbs(name) {
56+
return "", fmt.Errorf("illegal file path (absolute): %s", name)
57+
}
58+
dst := filepath.Join(workingDir, name)
59+
cleanBase := filepath.Clean(workingDir) + string(os.PathSeparator)
60+
if !strings.HasPrefix(dst, cleanBase) {
61+
return "", fmt.Errorf("illegal file path (traversal): %s", name)
62+
}
63+
return dst, nil
4464
}
4565

4666
func extractFiles(installationConfiguration *InstallationConfiguration, tarReader *tar.Reader) error {
67+
workingDir := installationConfiguration.WorkingDir()
4768
for {
4869
header, err := tarReader.Next()
4970

@@ -52,36 +73,49 @@ func extractFiles(installationConfiguration *InstallationConfiguration, tarReade
5273
}
5374

5475
if err != nil {
55-
log.Fatalf("ExtractTarGz: Next() failed: %s", err.Error())
76+
return fmt.Errorf("ExtractTarGz: Next() failed: %w", err)
5677
}
5778

5879
switch header.Typeflag {
5980
case tar.TypeDir:
60-
if err := os.Mkdir(header.Name, os.FileMode(dirDefault)); err != nil {
61-
log.Fatalf("ExtractTarGz: Mkdir() failed: %s", err.Error())
81+
dst, err := safeJoin(workingDir, header.Name)
82+
if err != nil {
83+
return err
6284
}
85+
if err := os.MkdirAll(dst, dirDefault); err != nil {
86+
return fmt.Errorf("ExtractTarGz: Mkdir() failed: %w", err)
87+
}
88+
6389
case tar.TypeReg:
64-
extractedFilePath := filepath.Join(installationConfiguration.WorkingDir(), header.Name)
65-
outFile, err := os.Create(extractedFilePath)
90+
extractedFilePath, err := safeJoin(workingDir, header.Name)
6691
if err != nil {
67-
log.Fatalf("ExtractTarGz: Create() failed: %s", err.Error())
92+
return err
6893
}
69-
if _, err = io.Copy(outFile, tarReader); err != nil {
70-
log.Fatalf("ExtractTarGz: Copy() failed: %s", err.Error())
94+
if err := os.MkdirAll(filepath.Dir(extractedFilePath), dirDefault); err != nil {
95+
return fmt.Errorf("ExtractTarGz: MkdirAll() failed: %w", err)
7196
}
72-
err = outFile.Close()
97+
outFile, err := os.Create(extractedFilePath)
7398
if err != nil {
99+
return fmt.Errorf("ExtractTarGz: Create() failed: %w", err)
100+
}
101+
if _, err = io.Copy(outFile, io.LimitReader(tarReader, maxExtractBytes)); err != nil {
102+
_ = outFile.Close()
103+
return fmt.Errorf("ExtractTarGz: Copy() failed: %w", err)
104+
}
105+
if err = outFile.Close(); err != nil {
74106
return err
75107
}
76-
err = os.Chmod(extractedFilePath, fs.ModePerm)
77-
if err != nil {
108+
// Preserve only the executable bit from the archive entry; never grant world-write.
109+
mode := os.FileMode(0644)
110+
if header.FileInfo().Mode()&0111 != 0 {
111+
mode = 0755
112+
}
113+
if err = os.Chmod(extractedFilePath, mode); err != nil {
78114
return err
79115
}
116+
80117
default:
81-
log.Fatalf(
82-
"ExtractTarGz: uknown type: %v in %s",
83-
header.Typeflag,
84-
header.Name)
118+
log.Printf("ExtractTarGz: unknown type: %v in %s", header.Typeflag, header.Name)
85119
}
86120
}
87121
return nil

0 commit comments

Comments
 (0)