Skip to content
This repository has been archived by the owner on Mar 29, 2023. It is now read-only.

fix: error when TAR has files outside of root #56

Merged
merged 3 commits into from Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 39 additions & 1 deletion tarwriter.go
Expand Up @@ -2,14 +2,22 @@ package files

import (
"archive/tar"
"errors"
"fmt"
"io"
"path"
"strings"
"time"
)

var (
ErrUnixFSPathOutsideRoot = errors.New("relative UnixFS paths outside the root are now allowed, use CAR instead")
)

type TarWriter struct {
TarW *tar.Writer
TarW *tar.Writer
baseDirSet bool
baseDir string
}

// NewTarWriter wraps given io.Writer into a new tar writer
Expand Down Expand Up @@ -50,8 +58,38 @@ func (w *TarWriter) writeFile(f File, fpath string) error {
return nil
}

func validateTarFilePath(baseDir, fpath string) bool {
// Ensure the filepath has no ".", "..", etc within the known root directory.
fpath = path.Clean(fpath)

// If we have a non-empty baseDir, check if the filepath starts with baseDir.
// If not, we can exclude it immediately. For 'ipfs get' and for the gateway,
// the baseDir would be '{cid}.tar'.
if baseDir != "" && !strings.HasPrefix(path.Clean(fpath), baseDir) {
return false
}

// If there is no defined baseDir, i.e., if there's multiple files in the
// root, check if paths contain elements that would otherwise make them fall
// outside the root.
if strings.Contains(fpath, "..") {
hacdias marked this conversation as resolved.
Show resolved Hide resolved
return false
}

return true
}

// WriteNode adds a node to the archive.
func (w *TarWriter) WriteFile(nd Node, fpath string) error {
if !w.baseDirSet {
w.baseDirSet = true // Use a variable for this as baseDir may be an empty string.
w.baseDir = fpath
}

if !validateTarFilePath(w.baseDir, fpath) {
return ErrUnixFSPathOutsideRoot
}

switch nd := nd.(type) {
case *Symlink:
return writeSymlinkHeader(w.TarW, nd.Target, fpath)
Expand Down
64 changes: 64 additions & 0 deletions tarwriter_test.go
Expand Up @@ -2,6 +2,7 @@ package files

import (
"archive/tar"
"errors"
"io"
"testing"
"time"
Expand Down Expand Up @@ -83,3 +84,66 @@ func TestTarWriter(t *testing.T) {
t.Fatal(err)
}
}

func TestTarWriterRelativePathInsideRoot(t *testing.T) {
tf := NewMapDirectory(map[string]Node{
"file.txt": NewBytesFile([]byte(text)),
"boop": NewMapDirectory(map[string]Node{
"../a.txt": NewBytesFile([]byte("bleep")),
"b.txt": NewBytesFile([]byte("bloop")),
}),
"beep.txt": NewBytesFile([]byte("beep")),
})

tw, err := NewTarWriter(io.Discard)
if err != nil {
t.Fatal(err)
}

defer tw.Close()
if err := tw.WriteFile(tf, ""); err != nil {
t.Error(err)
}
}

func TestTarWriterFailsFileOutsideRoot(t *testing.T) {
tf := NewMapDirectory(map[string]Node{
"file.txt": NewBytesFile([]byte(text)),
"boop": NewMapDirectory(map[string]Node{
"../../a.txt": NewBytesFile([]byte("bleep")),
"b.txt": NewBytesFile([]byte("bloop")),
}),
"beep.txt": NewBytesFile([]byte("beep")),
})

tw, err := NewTarWriter(io.Discard)
if err != nil {
t.Fatal(err)
}

defer tw.Close()
if err := tw.WriteFile(tf, ""); !errors.Is(err, ErrUnixFSPathOutsideRoot) {
t.Error(err)
hacdias marked this conversation as resolved.
Show resolved Hide resolved
}
}

func TestTarWriterFailsFileOutsideRootWithBaseDir(t *testing.T) {
tf := NewMapDirectory(map[string]Node{
"../file.txt": NewBytesFile([]byte(text)),
"boop": NewMapDirectory(map[string]Node{
"a.txt": NewBytesFile([]byte("bleep")),
"b.txt": NewBytesFile([]byte("bloop")),
}),
"beep.txt": NewBytesFile([]byte("beep")),
})

tw, err := NewTarWriter(io.Discard)
if err != nil {
t.Fatal(err)
}

defer tw.Close()
if err := tw.WriteFile(tf, "test.tar"); !errors.Is(err, ErrUnixFSPathOutsideRoot) {
t.Error(err)
hacdias marked this conversation as resolved.
Show resolved Hide resolved
}
}