Skip to content

Commit 6fa1665

Browse files
committed
attempt to resolve symlink problems
1 parent e46ba21 commit 6fa1665

File tree

3 files changed

+107
-8
lines changed

3 files changed

+107
-8
lines changed

extractor.go

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ import (
1111
)
1212

1313
type Extractor struct {
14-
Path string
15-
Progress func(int64) int64
14+
Path string
15+
Progress func(int64) int64
16+
sanitizePaths bool
17+
sanitizeLinks bool
18+
stubLinks bool
1619
}
1720

1821
func (te *Extractor) Extract(reader io.Reader) error {
@@ -61,13 +64,40 @@ func (te *Extractor) Extract(reader io.Reader) error {
6164
return nil
6265
}
6366

67+
// Sanitize toggles all output sanitation rules
68+
func (te *Extractor) Sanitize(toggle bool) {
69+
te.sanitizePaths = toggle
70+
te.sanitizeLinks = toggle
71+
}
72+
73+
// SanitizePaths toggles converting illegal paths to valid paths on Extract
74+
func (te *Extractor) SanitizePaths(toggle bool) {
75+
te.sanitizePaths = toggle
76+
77+
}
78+
79+
// SanitizeLinks toggles failure for links that are absolute or escape the output root on Extract
80+
func (te *Extractor) SanitizeLinks(toggle bool) {
81+
te.sanitizeLinks = toggle
82+
}
83+
84+
// StubLinks toggles link stubbing. When enabled, all links are created as empty files instead of links
85+
func (te *Extractor) StubLinks(toggle bool) {
86+
te.stubLinks = toggle
87+
}
88+
6489
// outputPath returns the path at which to place tarPath
6590
func (te *Extractor) outputPath(tarPath string) string {
66-
elems := strings.Split(tarPath, "/") // break into elems
67-
elems = elems[1:] // remove IPFS root
68-
safePath := platformSanitize(elems) // sanitize IPFS path to be platform legal
69-
safePath = fp.Join(te.Path, safePath) // rebase on to extraction target root
70-
return safePath
91+
var outPath string
92+
elems := strings.Split(tarPath, "/") // break into elems
93+
elems = elems[1:] // remove original root
94+
if te.sanitizePaths {
95+
outPath = platformSanitize(elems) // sanitize base path elements to be platform legal
96+
} else {
97+
outPath = fp.Join(elems...) // join elems
98+
}
99+
outPath = fp.Join(te.Path, outPath) // rebase on to extraction target root
100+
return outPath
71101
}
72102

73103
func (te *Extractor) extractDir(h *tar.Header, depth int) error {
@@ -82,6 +112,14 @@ func (te *Extractor) extractDir(h *tar.Header, depth int) error {
82112
}
83113

84114
func (te *Extractor) extractSymlink(h *tar.Header) error {
115+
if te.stubLinks {
116+
f, err := os.Create(te.outputPath(h.Name))
117+
f.Close()
118+
return err
119+
}
120+
if te.sanitizeLinks {
121+
return platformLink(te.Path, h.Linkname, te.outputPath(h.Name))
122+
}
85123
return os.Symlink(h.Linkname, te.outputPath(h.Name))
86124
}
87125

sanitize.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,29 @@
22

33
package tar
44

5-
import "path/filepath"
5+
import (
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
)
611

712
func platformSanitize(pathElements []string) string {
813
return filepath.Join(pathElements...)
914
}
15+
16+
//func platformLink(target, link string) error {
17+
//func platformLink(linkBase string, linkHeader *tar.Header) error {
18+
func platformLink(targetRoot, target, link string) error {
19+
//prevent symlinks from accessing outside of the output root
20+
resolvedTarget := filepath.Join(target, link)
21+
rel, err := filepath.Rel(targetRoot, resolvedTarget)
22+
if err != nil {
23+
return err
24+
}
25+
if strings.HasPrefix(rel, "..") {
26+
return fmt.Errorf("Symlink target %q escapes target root %q", target, targetRoot)
27+
}
28+
return os.Symlink(target, link)
29+
//return os.Symlink(h.Linkname, linkBase te.outputPath(h.Name))
30+
}

sanitize_windows.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ package tar
33
import (
44
"fmt"
55
"net/url"
6+
"os"
67
"path/filepath"
78
"regexp"
89
"strings"
10+
11+
"golang.org/x/sys/windows"
912
)
1013

1114
//https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
@@ -20,6 +23,8 @@ func init() {
2023
}
2124

2225
func platformSanitize(pathElements []string) string {
26+
//FIXME: what if a path starts with `\`
27+
2328
//first pass: strip illegal tail & prefix reserved names `CON .` -> `_CON`
2429
for pi := range pathElements {
2530
pathElements[pi] = strings.TrimRight(pathElements[pi], ". ") //MSDN: Do not end a file or directory name with a space or a period
@@ -54,3 +59,38 @@ func platformSanitize(pathElements []string) string {
5459

5560
return filepath.FromSlash(res)
5661
}
62+
63+
func platformLink(targetRoot, target, link string) error {
64+
if filepath.IsAbs(target) {
65+
return fmt.Errorf("Link target %q is an absolute path (forbidden)", target) //TODO: discuss
66+
}
67+
68+
if strings.HasPrefix(target, string(os.PathSeparator)) || strings.HasPrefix(target, "/") {
69+
return fmt.Errorf("Link target %q is relative to drive root (forbidden)", target) //TODO: discuss
70+
}
71+
72+
resolvedTarget := filepath.Join(link, target)
73+
rel, err := filepath.Rel(targetRoot, resolvedTarget)
74+
if err != nil {
75+
return err
76+
}
77+
78+
//disallow symlinks from climbing out of the target root
79+
if strings.HasPrefix(rel, "..") {
80+
return fmt.Errorf("Symlink target %q escapes target root %q", target, targetRoot)
81+
}
82+
//disallow pointing to self from parent ("../self" resolves to "self")
83+
if resolvedTarget == targetRoot {
84+
return fmt.Errorf("Symlink target %q points to itself %q", target, targetRoot)
85+
}
86+
87+
//TODO: placeholder url
88+
err = os.Symlink(target, link)
89+
if err != nil {
90+
if lErr, ok := err.(*os.LinkError); ok && lErr.Err == windows.ERROR_PRIVILEGE_NOT_HELD {
91+
return fmt.Errorf("Symlink creation privileges not held, see: https://github.com/ipfs/go-ipfs/blob/master/docs/windows.md#troubleshooting")
92+
}
93+
return err
94+
}
95+
return nil
96+
}

0 commit comments

Comments
 (0)