hashicorp / hashicorp/go-extract

Feature: Safe extraction of symlinks with absolut path (`/<whatever>`) as link target

Open
#77 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
33
Forks
2
PR merge metrics
No merged PRs in 30d

Description

Assuming the situation where we want extract an docker image. This image contains all files, directories and symlinks that an operating system would contain.

Taking the `ubuntu:latest` as an example. The extraction will fail, bc/ the directory stucture contains symlinks that points to the root filesystem:

```shell
~/tmp/docker-image% docker image save ubuntu:latest -o ubuntu-latest.tar
~/tmp/docker-image% goextract -v -c ubuntu-latest.tar ubuntu/
time=2024-08-01T11:24:25.779+02:00 level=INFO msg="start extraction" type=tar
time=2024-08-01T11:24:25.779+02:00 level=DEBUG msg=extract name=blobs/
time=2024-08-01T11:24:25.779+02:00 level=INFO msg="created destination directory" path=ubuntu/
time=2024-08-01T11:24:25.780+02:00 level=DEBUG msg=extract name=blobs/sha256/
time=2024-08-01T11:24:25.780+02:00 level=DEBUG msg=extract name=blobs/sha256/592f8a5be2100a50cd5f2122303f8b3e99e295a4b2f1776c38e2b3d4eb95937b
time=2024-08-01T11:24:25.780+02:00 level=DEBUG msg=extract name=blobs/sha256/aac3af10edc6612ad169ab9f9267dbe90e421f650a089ddc3f63546d9aacb3ed
time=2024-08-01T11:24:25.835+02:00 level=DEBUG msg=extract name=blobs/sha256/e32790107538738f33227bc08ef818d0915d8eac8eb72ab89a5fb131efa7e51b
time=2024-08-01T11:24:25.836+02:00 level=DEBUG msg=extract name=blobs/sha256/ffb64c9b7e8b9f1891f7a72609d4e691c4671dc4aa83084fc7b5774958d827de
time=2024-08-01T11:24:25.836+02:00 level=DEBUG msg=extract name=index.json
time=2024-08-01T11:24:25.836+02:00 level=DEBUG msg=extract name=manifest.json
time=2024-08-01T11:24:25.836+02:00 level=DEBUG msg=extract name=oci-layout
time=2024-08-01T11:24:25.836+02:00 level=DEBUG msg=extract name=repositories
~/tmp/docker-image% cd ubuntu/blobs/sha256
~/.../blobs/sha256% goextract -v -c --max-files=-1 aac3af10edc6612ad169ab9f9267dbe90e421f650a089ddc3f63546d9aacb3ed os-layer
time=2024-08-01T11:25:01.069+02:00 level=INFO msg="start extraction" type=tar
time=2024-08-01T11:25:01.069+02:00 level=DEBUG msg=extract name=bin
time=2024-08-01T11:25:01.070+02:00 level=INFO msg="created destination directory" path=os-layer
time=2024-08-01T11:25:01.070+02:00 level=DEBUG msg=extract name=boot/
time=2024-08-01T11:25:01.070+02:00 level=DEBUG msg=extract name=dev/
time=2024-08-01T11:25:01.070+02:00 level=DEBUG msg=extract name=etc/
time=2024-08-01T11:25:01.070+02:00 level=DEBUG msg=extract name=etc/.pwd.lock
time=2024-08-01T11:25:01.070+02:00 level=DEBUG msg=extract name=etc/alternatives/
time=2024-08-01T11:25:01.070+02:00 level=DEBUG msg=extract name=etc/alternatives/README
time=2024-08-01T11:25:01.071+02:00 level=DEBUG msg=extract name=etc/alternatives/awk
2024/08/01 11:25:01 error during extraction: failed to create safe symlink: symlink with absolute path as target: /usr/bin/mawk
```

Offering extraction of symlinks with absolut paths does not poses go-extract to be vulnerable to arbitrary file writes in the filesystem.

Assuming the situation that an archive contains following files:

```shell
% tar -ztvf malformed.tar
lrw------- 0 0 0 0 1 Jan 1970 root -> /
-rw------- 0 0 0 13 1 Jan 1970 root/escaped_file
```

The symlink `root -> /` would be extracted, but go-extract prevents arbitrary writes by inventing the option `config.FollowSymlinks()`.

https://github.com/hashicorp/go-extract/blob/ffdb27fec806e03f683945eda30de91f193fff35/extractor/target.go#L240-L251

The extraction of `root/escaped_file` would be blocked.

To ensure that go-extract will not be vulnerable to a link-write attack, the sanity check for file overwrite need to be adjusted. Before the call to [`target.CreateFile`](https://github.com/hashicorp/go-extract/blob/ffdb27fec806e03f683945eda30de91f193fff35/extractor/target.go#L49) is performed, we need to check if the target exist, is a symlink (with `fi, err := target.Lstat; err == nil && fi.Mode & os.ModeSymlink != 0`) and if following symlinks (`config.FollowSymlinks() == true`) is enabled.

By offering this feature, we can still extract archives, but puts the burden on the consumer to ensure that they are aware of the possible consumption of symlinks that point to arbitrary locations in the file system

Contributor guide

Open the contributing guide

Research direction

Read extractor/target.go, especially the safe-symlink checks around target.CreateFile and the linked lines. Trace how Lstat and config.FollowSymlinks() currently affect overwrites, then verify that absolute symlinks can be extracted when explicitly enabled without allowing linked files to cause arbitrary writes.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
security
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.