fix(R-1187 b): the import writes only through os.Root — never through a link that leaves the app's folder
Found by the automated security review of dfd0f8e. Red-proved. Commit only.
This commit is contained in:
@@ -1204,7 +1204,7 @@ func extractTarGz(tgzPath, destDir string) error {
|
||||
}
|
||||
|
||||
// lchownFn is the owner seam (a test records it; production is os.Lchown).
|
||||
var lchownFn = os.Lchown
|
||||
var lchownFn = func(r *os.Root, name string, uid, gid int) error { return r.Lchown(name, uid, gid) }
|
||||
|
||||
// extractTar extracts a plain tar archive (one drive folder) into destDir and returns the entries it REFUSED.
|
||||
//
|
||||
@@ -1219,6 +1219,14 @@ func extractTar(tarPath, destDir string) ([]string, error) {
|
||||
}
|
||||
defer f.Close()
|
||||
root := filepath.Clean(destDir)
|
||||
// Every write goes through os.Root: no entry can create or write a file outside destDir, not even through a link
|
||||
// already in the folder (left by the old app, or made by an earlier entry) that points out — the security review's
|
||||
// case, pinned by TestR1187b_NoWriteThroughALinkThatLeavesTheFolder.
|
||||
rt, err := os.OpenRoot(root)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rt.Close()
|
||||
var refused []string
|
||||
tr := tar.NewReader(f)
|
||||
for {
|
||||
@@ -1229,53 +1237,59 @@ func extractTar(tarPath, destDir string) ([]string, error) {
|
||||
if err != nil {
|
||||
return refused, err
|
||||
}
|
||||
target := filepath.Join(root, hdr.Name)
|
||||
if !strings.HasPrefix(filepath.Clean(target), root+string(os.PathSeparator)) {
|
||||
name := filepath.Clean(hdr.Name)
|
||||
if name == "." || filepath.IsAbs(name) || name == ".." || strings.HasPrefix(name, ".."+string(os.PathSeparator)) {
|
||||
continue // path traversal
|
||||
}
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeDir:
|
||||
if err := os.MkdirAll(target, os.FileMode(hdr.Mode).Perm()); err != nil {
|
||||
if err := rt.MkdirAll(name, os.FileMode(hdr.Mode).Perm()); err != nil {
|
||||
return refused, err
|
||||
}
|
||||
os.Chmod(target, os.FileMode(hdr.Mode).Perm())
|
||||
rt.Chmod(name, os.FileMode(hdr.Mode).Perm())
|
||||
case tar.TypeReg:
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||
return refused, err
|
||||
if dir := filepath.Dir(name); dir != "." {
|
||||
if err := rt.MkdirAll(dir, 0755); err != nil {
|
||||
return refused, err
|
||||
}
|
||||
}
|
||||
outFile, err := os.Create(target)
|
||||
out, err := rt.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(hdr.Mode).Perm())
|
||||
if err != nil {
|
||||
refused = append(refused, hdr.Name) // e.g. its path goes through a link that leaves the folder
|
||||
continue
|
||||
}
|
||||
if _, err := io.Copy(out, tr); err != nil {
|
||||
out.Close()
|
||||
return refused, err
|
||||
}
|
||||
if _, err := io.Copy(outFile, tr); err != nil {
|
||||
outFile.Close()
|
||||
return refused, err
|
||||
}
|
||||
outFile.Close()
|
||||
os.Chmod(target, os.FileMode(hdr.Mode).Perm())
|
||||
out.Close()
|
||||
rt.Chmod(name, os.FileMode(hdr.Mode).Perm())
|
||||
case tar.TypeSymlink:
|
||||
dst := hdr.Linkname
|
||||
if filepath.IsAbs(dst) {
|
||||
refused = append(refused, hdr.Name)
|
||||
continue
|
||||
}
|
||||
resolved := filepath.Clean(filepath.Join(filepath.Dir(target), dst))
|
||||
resolved := filepath.Clean(filepath.Join(filepath.Dir(filepath.Join(root, name)), dst))
|
||||
if resolved != root && !strings.HasPrefix(resolved, root+string(os.PathSeparator)) {
|
||||
refused = append(refused, hdr.Name)
|
||||
continue
|
||||
}
|
||||
if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
|
||||
return refused, err
|
||||
if dir := filepath.Dir(name); dir != "." {
|
||||
if err := rt.MkdirAll(dir, 0755); err != nil {
|
||||
return refused, err
|
||||
}
|
||||
}
|
||||
os.Remove(target)
|
||||
if err := os.Symlink(dst, target); err != nil {
|
||||
return refused, err
|
||||
rt.Remove(name)
|
||||
if err := rt.Symlink(dst, name); err != nil {
|
||||
refused = append(refused, hdr.Name)
|
||||
continue
|
||||
}
|
||||
default:
|
||||
refused = append(refused, hdr.Name)
|
||||
continue
|
||||
}
|
||||
if err := lchownFn(target, hdr.Uid, hdr.Gid); err != nil && !os.IsPermission(err) {
|
||||
if err := lchownFn(rt, name, hdr.Uid, hdr.Gid); err != nil && !os.IsPermission(err) {
|
||||
return refused, fmt.Errorf("setting the owner of %s: %w", hdr.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user