Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(pkg,internal): multiple fixes related to cos and driver build. #383

Merged
merged 3 commits into from
Jan 23, 2024
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 cmd/artifact/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ func (o *artifactInstallOptions) RunArtifactInstall(ctx context.Context, args []
return err
}
// Extract artifact and move it to its destination directory
_, err = utils.ExtractTarGz(f, destDir, 0)
_, err = utils.ExtractTarGz(ctx, f, destDir, 0)
if err != nil {
return fmt.Errorf("cannot extract %q to %q: %w", result.Filename, destDir, err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/follower/follower.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ func (f *Follower) pull(ctx context.Context) (filePaths []string, res *oci.Regis
}

// Extract artifact and move it to its destination directory
filePaths, err = utils.ExtractTarGz(file, f.tmpDir, 0)
filePaths, err = utils.ExtractTarGz(ctx, file, f.tmpDir, 0)
if err != nil {
return filePaths, res, fmt.Errorf("unable to extract %q to %q: %w", res.Filename, f.tmpDir, err)
}
Expand Down
108 changes: 91 additions & 17 deletions internal/utils/extract.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,47 +24,77 @@ import (
"os"
"path/filepath"
"strings"

"golang.org/x/net/context"
)

type link struct {
Name string
Path string
}

// ExtractTarGz extracts a *.tar.gz compressed archive and moves its content to destDir.
// Returns a slice containing the full path of the extracted files.
func ExtractTarGz(gzipStream io.Reader, destDir string, stripPathComponents int) ([]string, error) {
var files []string
func ExtractTarGz(ctx context.Context, gzipStream io.Reader, destDir string, stripPathComponents int) ([]string, error) {
Copy link
Contributor Author

@FedeDP FedeDP Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new signature takes the context too!

var (
files []string
links []link
symlinks []link
err error
)

// We need an absolute path
destDir, err = filepath.Abs(destDir)
if err != nil {
return nil, err
}

uncompressedStream, err := gzip.NewReader(gzipStream)
if err != nil {
return nil, err
}

tarReader := tar.NewReader(uncompressedStream)

for {
header, err := tarReader.Next()
select {
case <-ctx.Done():
return nil, errors.New("interrupted")
default:
}

header, err := tarReader.Next()
Fixed Show fixed Hide fixed
if errors.Is(err, io.EOF) {
break
}

if err != nil {
return nil, err
}

if strings.Contains(header.Name, "..") {
return nil, fmt.Errorf("not allowed relative path in tar archive")
}

strippedName := stripComponents(header.Name, stripPathComponents)
path := header.Name
if stripPathComponents > 0 {
path = stripComponents(path, stripPathComponents)
}
if path == "" {
continue
}

if path, err = safeConcat(destDir, filepath.Clean(path)); err != nil {
// Skip paths that would escape destDir
continue
}
info := header.FileInfo()
files = append(files, path)

switch header.Typeflag {
case tar.TypeDir:
d := filepath.Join(destDir, strippedName)
if err = os.Mkdir(filepath.Clean(d), 0o750); err != nil {
if err = os.MkdirAll(path, info.Mode()); err != nil {
return nil, err
}
files = append(files, d)
case tar.TypeReg:
f := filepath.Join(destDir, strippedName)
outFile, err := os.Create(filepath.Clean(f))
outFile, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, info.Mode())
if err != nil {
return nil, err
}
Expand All @@ -76,25 +106,69 @@ func ExtractTarGz(gzipStream io.Reader, destDir string, stripPathComponents int)
if err = outFile.Close(); err != nil {
return nil, err
}
files = append(files, f)
case tar.TypeLink:
name := header.Linkname
if stripPathComponents > 0 {
name = stripComponents(name, stripPathComponents)
}
if name == "" {
continue
}

name = filepath.Join(destDir, filepath.Clean(name))
links = append(links, link{Path: path, Name: name})
case tar.TypeSymlink:
symlinks = append(symlinks, link{Path: path, Name: header.Linkname})
default:
return nil, fmt.Errorf("extractTarGz: uknown type: %b in %s", header.Typeflag, header.Name)
}
}

// Now we make another pass creating the links
for i := range links {
select {
case <-ctx.Done():
return nil, errors.New("interrupted")
default:
}
if err = os.Link(links[i].Name, links[i].Path); err != nil {
return nil, err
}
}

for i := range symlinks {
select {
case <-ctx.Done():
return nil, errors.New("interrupted")
default:
}
if err = os.Symlink(symlinks[i].Name, symlinks[i].Path); err != nil {
return nil, err
}
}
return files, nil
}

func stripComponents(headerName string, stripComponents int) string {
if stripComponents == 0 {
return headerName
}
names := strings.FieldsFunc(headerName, func(r rune) bool {
return r == os.PathSeparator
})
names := strings.Split(headerName, string(filepath.Separator))
if len(names) < stripComponents {
return headerName
}
return filepath.Clean(strings.Join(names[stripComponents:], "/"))
return filepath.Clean(strings.Join(names[stripComponents:], string(filepath.Separator)))
}

// safeConcat concatenates destDir and name
// but returns an error if the resulting path points outside 'destDir'.
func safeConcat(destDir, name string) (string, error) {
res := filepath.Join(destDir, name)
if !strings.HasSuffix(destDir, string(os.PathSeparator)) {
destDir += string(os.PathSeparator)
}
if !strings.HasPrefix(res, destDir) {
return res, fmt.Errorf("unsafe path concatenation: '%s' with '%s'", destDir, name)
}
return res, nil
}
210 changes: 210 additions & 0 deletions internal/utils/extract_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (C) 2024 The Falco Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package utils

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

"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)

const (
srcDir = "./foo"
)

var (
files = []string{srcDir + "/example.txt", srcDir + "/test.txt", srcDir + "/bar/baz.txt"}
)

func createTarball(t *testing.T, tarballFilePath, srcDir string) {
file, err := os.Create(tarballFilePath)
assert.NoError(t, err)
defer file.Close()

gzipWriter := gzip.NewWriter(file)
defer gzipWriter.Close()

tarWriter := tar.NewWriter(gzipWriter)
defer tarWriter.Close()

err = filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
return addToArchive(tarWriter, path, info)
})
assert.NoError(t, err)
}

func addToArchive(tw *tar.Writer, fullName string, info os.FileInfo) error {
// Open the file which will be written into the archive
file, err := os.Open(fullName)
if err != nil {
return err
}
defer file.Close()

// Create a tar Header from the FileInfo data
header, err := tar.FileInfoHeader(info, info.Name())
if err != nil {
return err
}

// Use full path as name (FileInfoHeader only takes the basename)
// If we don't do this the directory strucuture would
// not be preserved
// https://golang.org/src/archive/tar/common.go?#L626
header.Name = fullName

// Write file header to the tar archive
err = tw.WriteHeader(header)
if err != nil {
return err
}

if !info.IsDir() {
// Copy file content to tar archive
_, err = io.Copy(tw, file)
if err != nil {
return err
}
}

return nil
}

func TestExtractTarGz(t *testing.T) {
// Create src dir
err := os.MkdirAll(srcDir, 0o750)
assert.NoError(t, err)
t.Cleanup(func() {
_ = os.RemoveAll(srcDir)
})

// Generate files to be tarballed
for _, f := range files {
err := os.MkdirAll(filepath.Dir(f), 0o755)
assert.NoError(t, err)
_, err = os.Create(f)
assert.NoError(t, err)
}

// create tarball
createTarball(t, "./test.tgz", srcDir)
t.Cleanup(func() {
_ = os.RemoveAll("./test.tgz")
})

// Create dest folder
destDir := "./test"
err = os.MkdirAll(destDir, 0o750)
assert.NoError(t, err)
t.Cleanup(func() {
_ = os.RemoveAll(destDir)
})

// Extract tarball
f, err := os.Open("./test.tgz")
assert.NoError(t, err)
t.Cleanup(func() {
f.Close()
})

list, err := ExtractTarGz(context.TODO(), f, destDir, 0)
assert.NoError(t, err)

// Final checks
assert.NotEmpty(t, list)

// All extracted files are ok
for _, f := range list {
_, err := os.Stat(f)
assert.NoError(t, err)
}

// Extracted folder contains all source files (plus folders)
absDestDir, err := filepath.Abs(destDir)
assert.NoError(t, err)
for _, f := range files {
path := filepath.Join(absDestDir, f)
assert.Contains(t, list, path)
}
}

func TestExtractTarGzStripComponents(t *testing.T) {
// Create src dir
srcDir := "./foo"
err := os.MkdirAll(srcDir, 0o750)
assert.NoError(t, err)
t.Cleanup(func() {
_ = os.RemoveAll(srcDir)
})

// Generate files to be tarballed
for _, f := range files {
err := os.MkdirAll(filepath.Dir(f), 0o755)
assert.NoError(t, err)
_, err = os.Create(f)
assert.NoError(t, err)
}

// create tarball
createTarball(t, "./test.tgz", srcDir)
t.Cleanup(func() {
_ = os.RemoveAll("./test.tgz")
})

// Create dest folder
destdirStrip := "./test_strip"
err = os.MkdirAll(destdirStrip, 0o750)
assert.NoError(t, err)
t.Cleanup(func() {
_ = os.RemoveAll(destdirStrip)
})

// Extract tarball
f, err := os.Open("./test.tgz")
assert.NoError(t, err)
t.Cleanup(func() {
f.Close()
})
// NOTE that here we strip first component
list, err := ExtractTarGz(context.TODO(), f, destdirStrip, 1)
assert.NoError(t, err)

// Final checks
assert.NotEmpty(t, list)

// All extracted files are ok
for _, f := range list {
_, err := os.Stat(f)
assert.NoError(t, err)
}

// Extracted folder contains all source files (plus folders)
absDestDirStrip, err := filepath.Abs(destdirStrip)
assert.NoError(t, err)
for _, f := range files {
// We stripped first component (ie: srcDir)
ff := strings.TrimPrefix(f, srcDir)
path := filepath.Join(absDestDirStrip, ff)
assert.Contains(t, list, path)
}
}
Loading