Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ go get github.com/intel/oneapi-cli
Alternatively see the tags/releases for a binary build for your OS.

## Building
Go 1.26.2 should be used to build the CLI/TUI app.
Go 1.26.3 should be used to build the CLI/TUI app.

```bash
git clone https://github.com/intel/oneapi-cli.git
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/intel/oneapi-cli

go 1.26.2
go 1.26.3

require (
github.com/gdamore/tcell v1.4.0
Expand Down
75 changes: 50 additions & 25 deletions pkg/extractor/tar.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
// Copyright 2019 Intel Corporation
// Copyright 2019-2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
package extractor

import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"strings"
)

// secure join to prevent path traversal
func safeJoin(baseDir, name string) (string, error) {
base := filepath.Clean(baseDir)
cleanName := filepath.Clean(name)

// Reject absolute paths
if filepath.IsAbs(cleanName) {
return "", fmt.Errorf("absolute paths not allowed: %s", name)
}

target := filepath.Join(base, cleanName)

// Ensure target is within base
rel, err := filepath.Rel(base, target)
if err != nil {
return "", err
}
if strings.HasPrefix(rel, "..") {
return "", fmt.Errorf("path traversal detected: %s", name)
}

return target, nil
}

// ExtractTarGz extracts a tar.gz to the destination
func ExtractTarGz(sourcetb string, out string) error {

//Ensure Output exists
// Ensure Output exists
if err := os.MkdirAll(out, 0750); err != nil {
return err
}
Expand All @@ -22,6 +48,7 @@ func ExtractTarGz(sourcetb string, out string) error {
if err != nil {
return err
}
defer tbz.Close()

gzr, err := gzip.NewReader(tbz)
if err != nil {
Expand All @@ -35,55 +62,53 @@ func ExtractTarGz(sourcetb string, out string) error {
hdr, err := tr.Next()

switch {

case err == io.EOF:
return nil // return when no more files, good path

return nil
case err != nil:
return err
}

// the target location where the dir/file should be created
target := filepath.Join(out, hdr.Name)
// PTK0006726
target, err := safeJoin(out, hdr.Name)
if err != nil {
return err
}

// check the file type, are we a directory for example
switch hdr.Typeflag {

case tar.TypeDir:
if err := os.MkdirAll(target, os.FileMode(hdr.Mode)); err != nil {
return err
}

// we have a file, create it with the stored attr from the header
case tar.TypeReg:
//Sometimes the file can come before its directory listing, or it never has one :S
if !fileExists(filepath.Dir(target)) {
if err := os.MkdirAll(filepath.Dir(target), 0750); err != nil {
return err
}
// Ensure parent dir exists
if err := os.MkdirAll(filepath.Dir(target), 0750); err != nil {
return err
}

f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode))
// Safer file write (truncate if exists)
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(hdr.Mode))
if err != nil {
return err
}

// Store into destination
if _, err := io.Copy(f, tr); err != nil {
f.Close()
return err
}

err = f.Close()
if err != nil {
if err := f.Close(); err != nil {
return err
}
}
}
}

func fileExists(path string) bool {
if _, err := os.Stat(path); os.IsNotExist(err) {
return false
// 🔒 Block symlinks & hardlinks (critical)
case tar.TypeSymlink, tar.TypeLink:
return fmt.Errorf("unsupported file type (link): %s", hdr.Name)

default:
// Optional: ignore or fail
return fmt.Errorf("unsupported file type: %v in %s", hdr.Typeflag, hdr.Name)
}
}
return true
}
23 changes: 22 additions & 1 deletion pkg/extractor/tar_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2019 Intel Corporation
// Copyright 2019-2026 Intel Corporation
// SPDX-License-Identifier: BSD-3-Clause
package extractor

Expand Down Expand Up @@ -92,3 +92,24 @@ func TestOKTarGz(t *testing.T) {
t.Error(err) // Failed to run against ok-ish tar
}
}

func TestExtractTarGz_PathTraversalBlocked(t *testing.T) {

traversalTar := filepath.Join("testdata", "tarslip.tar.gz")

tempPath := setupGoldTemp(t)
defer cleanupTemp(t, tempPath)

output := filepath.Join(tempPath, "extract")

err := ExtractTarGz(traversalTar, output)
if err == nil {
t.Fatal("expected path traversal error, got nil")
}

escapedFile := filepath.Join(tempPath, "TARSLIP_PWNED.txt")

if _, err := os.Stat(escapedFile); !os.IsNotExist(err) {
t.Errorf("path traversal file was created outside extraction dir")
}
}