Skip to content
Merged
9 changes: 3 additions & 6 deletions packages/orchestrator/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ require (
github.com/bmatcuk/doublestar/v4 v4.9.1
github.com/caarlos0/env/v11 v11.3.1
github.com/containernetworking/plugins v1.9.0
github.com/containers/storage v1.58.0
github.com/coreos/go-iptables v0.8.0
github.com/dustin/go-humanize v1.0.1
github.com/e2b-dev/infra/packages/clickhouse v0.0.0
Expand All @@ -47,6 +46,7 @@ require (
github.com/jellydator/ttlcache/v3 v3.4.0
github.com/launchdarkly/go-sdk-common/v3 v3.3.0
github.com/launchdarkly/go-server-sdk/v7 v7.13.0
github.com/moby/go-archive v0.2.0
github.com/moby/moby/api v1.54.2
github.com/ngrok/firewall_toolkit v0.0.18
github.com/oapi-codegen/gin-middleware v1.0.2
Expand Down Expand Up @@ -217,7 +217,6 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.6 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/parsers/yaml v1.1.0 // indirect
github.com/knadh/koanf/providers/env v1.1.0 // indirect
Expand Down Expand Up @@ -246,11 +245,11 @@ require (
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/go-archive v0.2.0 // indirect
github.com/moby/moby/client v0.4.1 // indirect
github.com/moby/patternmatcher v0.6.1 // indirect
github.com/moby/sys/capability v0.4.0 // indirect
github.com/moby/sys/mount v0.3.4 // indirect
github.com/moby/sys/mountinfo v0.7.2 // indirect
github.com/moby/sys/reexec v0.1.0 // indirect
github.com/moby/sys/sequential v0.6.0 // indirect
github.com/moby/sys/user v0.4.0 // indirect
github.com/moby/sys/userns v0.1.0 // indirect
Expand All @@ -268,7 +267,6 @@ require (
github.com/onsi/gomega v1.38.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opencontainers/runtime-spec v1.3.0 // indirect
github.com/orcaman/concurrent-map/v2 v2.0.1 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/paulmach/orb v0.11.1 // indirect
Expand Down Expand Up @@ -299,7 +297,6 @@ require (
github.com/tklauser/numcpus v0.11.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/ulikunitz/xz v0.5.14 // indirect
github.com/vektra/mockery/v3 v3.7.0 // indirect
github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect
github.com/woodsbury/decimal128 v1.4.0 // indirect
Expand Down
14 changes: 4 additions & 10 deletions packages/orchestrator/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 8 additions & 5 deletions packages/orchestrator/pkg/template/build/core/oci/oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import (
"strconv"
"strings"

"github.com/containers/storage/pkg/archive"
"github.com/dustin/go-humanize"
"github.com/google/go-containerregistry/pkg/name"
containerregistry "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
"github.com/moby/go-archive"
"github.com/moby/go-archive/chrootarchive"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.uber.org/zap"
Expand Down Expand Up @@ -440,10 +441,12 @@ func createExport(ctx context.Context, logger logger.Logger, srcImage containerr
return fmt.Errorf("failed to get uncompressed layer %d: %w", i, err)
}
defer rc.Close()

err = archive.Untar(rc, layerPath, &archive.TarOptions{
IgnoreChownErrors: true,
WhiteoutFormat: archive.OverlayWhiteoutFormat,
// Use chrootarchive (same mechanism as the Docker daemon's layer unpacking).
// Note: layers with redundant nested whiteouts (.wh.foo + foo/.wh.bar)
// are not supported and fail here with ENOTDIR — same as stock Docker
// (both overlay2 and containerd snapshotter reject such images).
err = chrootarchive.UntarUncompressed(rc, layerPath, &archive.TarOptions{
WhiteoutFormat: archive.OverlayWhiteoutFormat,
Comment thread
jakubno marked this conversation as resolved.
})
if err != nil {
return fmt.Errorf("failed to untar layer %d: %w", i, err)
Expand Down
76 changes: 76 additions & 0 deletions packages/orchestrator/pkg/template/build/core/oci/oci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,82 @@ func TestCreateExportHandlesOCIWhiteout(t *testing.T) {
assert.NotEqual(t, os.FileMode(0), info.Mode()&os.ModeCharDevice, "stale-file.txt in upper layer must be a character device (OCI whiteout), got %s", info.Mode())
}

// TestCreateExportPreservesEscapingRelativeSymlink reproduces layers produced
// by `nix store optimise`: /nix/store/.links contains symlinks whose relative
// targets (e.g. ../../../../../etc/environment) lexically escape the layer
// directory but are clamped at / by the kernel at runtime
func TestCreateExportPreservesEscapingRelativeSymlink(t *testing.T) {
t.Parallel()

ctx := t.Context()
logger := logger.NewNopLogger()

var buf bytes.Buffer
tw := tar.NewWriter(&buf)

for _, dir := range []string{"nix", "nix/store", "nix/store/.links", "etc"} {
require.NoError(t, tw.WriteHeader(&tar.Header{
Name: dir + "/",
Typeflag: tar.TypeDir,
Mode: 0o755,
}))
}

fileContent := []byte("PATH=/usr/bin\n")
require.NoError(t, tw.WriteHeader(&tar.Header{
Name: "etc/environment",
Typeflag: tar.TypeReg,
Mode: 0o644,
Size: int64(len(fileContent)),
}))
_, err := tw.Write(fileContent)
require.NoError(t, err)

// The problematic entry: 5 levels up from a directory only 3 levels deep.
escapingTarget := "../../../../../etc/environment"
require.NoError(t, tw.WriteHeader(&tar.Header{
Name: "nix/store/.links/abc",
Typeflag: tar.TypeSymlink,
Linkname: escapingTarget,
Mode: 0o777,
}))

// A regular relative symlink that stays inside the layer.
require.NoError(t, tw.WriteHeader(&tar.Header{
Name: "nix/store/link-within",
Typeflag: tar.TypeSymlink,
Linkname: "../../etc/environment",
Mode: 0o777,
}))

require.NoError(t, tw.Close())

img := empty.Image
layer, err := tarball.LayerFromOpener(func() (io.ReadCloser, error) {
return io.NopCloser(bytes.NewReader(buf.Bytes())), nil
})
require.NoError(t, err)
img, err = mutate.AppendLayers(img, layer)
require.NoError(t, err)

layerPaths, err := createExport(ctx, logger, img, t.TempDir())
require.NoError(t, err)
require.Len(t, layerPaths, 1)
layerPath := layerPaths[0]

target, err := os.Readlink(filepath.Join(layerPath, "nix/store/.links/abc"))
require.NoError(t, err)
assert.Equal(t, escapingTarget, target, "symlink target must be preserved verbatim")

target, err = os.Readlink(filepath.Join(layerPath, "nix/store/link-within"))
require.NoError(t, err)
assert.Equal(t, "../../etc/environment", target)

content, err := os.ReadFile(filepath.Join(layerPath, "etc/environment"))
require.NoError(t, err)
assert.Equal(t, fileContent, content)
}

// authHandler wraps a registry handler with basic authentication
func authHandler(handler http.Handler, username, password string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
Loading