@@ -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
2328func 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
4666func 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