Skip to content

Commit

Permalink
Add support for UPX files
Browse files Browse the repository at this point in the history
Signed-off-by: egibs <20933572+egibs@users.noreply.github.com>
  • Loading branch information
egibs committed Dec 19, 2024
1 parent 6e326a4 commit 1b03097
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 8 deletions.
26 changes: 19 additions & 7 deletions pkg/archive/archive.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,15 @@ func extractNestedArchive(
if err != nil {
return fmt.Errorf("failed to determine file type: %w", err)
}
if ft != nil && ft.MIME == "application/zlib" {
switch {
case ft != nil && ft.MIME == "application/x-upx":
isArchive = true
}
if _, ok := programkind.ArchiveMap[programkind.GetExt(f)]; ok {
case ft != nil && ft.MIME == "application/zlib":
isArchive = true
case programkind.ArchiveMap[programkind.GetExt(f)]:
isArchive = true
}

//nolint:nestif // ignore complexity of 8
if isArchive {
// Ensure the file was extracted and exists
Expand All @@ -52,11 +55,15 @@ func extractNestedArchive(
if err != nil {
return fmt.Errorf("failed to determine file type: %w", err)
}
if ft != nil && ft.MIME == "application/zlib" {
switch {
case ft != nil && ft.MIME == "application/x-upx":
extract = ExtractUPX
case ft != nil && ft.MIME == "application/zlib":
extract = ExtractZlib
} else {
default:
extract = ExtractionMethod(programkind.GetExt(fullPath))
}

err = extract(ctx, d, fullPath)
if err != nil {
return fmt.Errorf("extract nested archive: %w", err)
Expand Down Expand Up @@ -103,11 +110,16 @@ func ExtractArchiveToTempDir(ctx context.Context, path string) (string, error) {
if err != nil {
return "", fmt.Errorf("failed to determine file type: %w", err)
}
if ft != nil && ft.MIME == "application/zlib" {

switch {
case ft != nil && ft.MIME == "application/zlib":
extract = ExtractZlib
} else {
case ft != nil && ft.MIME == "application/x-upx":
extract = ExtractUPX
default:
extract = ExtractionMethod(programkind.GetExt(path))
}

if extract == nil {
return "", fmt.Errorf("unsupported archive type: %s", path)
}
Expand Down
68 changes: 68 additions & 0 deletions pkg/archive/upx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package archive

import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"

"github.com/chainguard-dev/clog"
)

var ErrUPXNotFound = errors.New("UPX executable not found in PATH")

func upxInstalled() error {
_, err := exec.LookPath("upx")
if err != nil {
if errors.Is(err, exec.ErrNotFound) {
return ErrUPXNotFound
}
return fmt.Errorf("failed to check for UPX executable: %w", err)
}
return nil
}

func ExtractUPX(ctx context.Context, d, f string) error {
// Check if UPX is installed
if err := upxInstalled(); err != nil {
return err
}

logger := clog.FromContext(ctx).With("dir", d, "file", f)
logger.Debug("extracting upx")

// Check if the file is valid
_, err := os.Stat(f)
if err != nil {
return fmt.Errorf("failed to stat file: %w", err)
}

gf, err := os.Open(f)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer gf.Close()

base := filepath.Base(f)
target := filepath.Join(d, base[:len(base)-len(filepath.Ext(base))])

// copy the file to the temporary directory before decompressing
tf, err := os.ReadFile(f)
if err != nil {
return err
}

err = os.WriteFile(target, tf, 0o600)
if err != nil {
return err
}

cmd := exec.Command("upx", "-d", target)
if _, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to decompress upx file: %w", err)
}

return nil
}
18 changes: 17 additions & 1 deletion pkg/programkind/programkind.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package programkind

import (
"bytes"
"errors"
"fmt"
"io"
Expand All @@ -30,6 +31,7 @@ var ArchiveMap = map[string]bool{
".tar.gz": true,
".tar.xz": true,
".tgz": true,
".upx": true,
".whl": true,
".xz": true,
".zip": true,
Expand Down Expand Up @@ -86,6 +88,7 @@ var supportedKind = map[string]string{
"sh": "application/x-sh",
"so": "application/x-sharedlib",
"ts": "application/typescript",
"upx": "application/x-upx",
"whl": "application/x-wheel+zip",
"yaml": "",
"yara": "",
Expand All @@ -99,8 +102,17 @@ type FileType struct {
}

// IsSupportedArchive returns whether a path can be processed by our archive extractor.
// UPX files are an edge case since they may or may not even have an extension that can be referenced.
func IsSupportedArchive(path string) bool {
return ArchiveMap[GetExt(path)]
if _, isValidArchive := ArchiveMap[GetExt(path)]; isValidArchive {
return true
}
if ft, err := File(path); err == nil && ft != nil {
if ft.MIME == "application/x-upx" {
return true
}
}
return false
}

// getExt returns the extension of a file path
Expand Down Expand Up @@ -206,6 +218,10 @@ func File(path string) (*FileType, error) {
// final strategy: DIY matching where mimetype is too strict.
s := string(hdr[:])
switch {
// Check for UPX files before we do the ELF check
// We're looking for UPX! throughout the header since it may not be in the first 2-4 bytes
case bytes.Contains(hdr[:], []byte{'\x55', '\x50', '\x58', '\x21'}):
return Path(".upx"), nil
case hdr[0] == '\x7f' && hdr[1] == 'E' || hdr[2] == 'L' || hdr[3] == 'F':
return Path(".elf"), nil
case strings.Contains(s, "<?php"):
Expand Down

0 comments on commit 1b03097

Please sign in to comment.