mirror of
https://github.com/moby/buildkit.git
synced 2026-11-06 05:16:45 +00:00
exporter: sanitize platform IDs in paths
Replace Windows path separators and drive separators when platform IDs are used as local and tar exporter path components. Add a regression test for tar exporter output generated from frontend-controlled platform metadata. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
1 parent
0ad2365122
commit
14ba4099c8
7 files changed
+153
-5
No files matched your search
@@ -11,6 +11,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -443,6 +444,90 @@ func testExportLocalNoPlatformSplitOverwrite(t *testing.T, sb integration.Sandbo
|
||||
require.ErrorContains(t, err, "when split option is disabled")
|
||||
}
|
||||
|
||||
func testExportTarPlatformIDSanitized(t *testing.T, sb integration.Sandbox) {
|
||||
workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter, workers.FeatureMultiPlatform)
|
||||
c, err := New(sb.Context(), sb.Address())
|
||||
require.NoError(t, err)
|
||||
defer c.Close()
|
||||
|
||||
const platformID = `..\buildkit-outside`
|
||||
platform := platforms.DefaultSpec()
|
||||
|
||||
frontend := func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
||||
st := llb.Scratch().File(
|
||||
llb.Mkfile("payload.txt", 0600, []byte("payload")),
|
||||
)
|
||||
|
||||
def, err := st.Marshal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := c.Solve(ctx, gateway.SolveRequest{
|
||||
Definition: def.ToPB(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref, err := r.SingleRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := gateway.NewResult()
|
||||
res.AddRef(platformID, ref)
|
||||
|
||||
dt, err := json.Marshal(&exptypes.Platforms{
|
||||
Platforms: []exptypes.Platform{{
|
||||
ID: platformID,
|
||||
Platform: platform,
|
||||
}},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res.AddMeta(exptypes.ExporterPlatformsKey, dt)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
outW := bytes.NewBuffer(nil)
|
||||
_, err = c.Build(sb.Context(), SolveOpt{
|
||||
Exports: []ExportEntry{
|
||||
{
|
||||
Type: ExporterTar,
|
||||
Output: fixedWriteCloser(&iohelper.NopWriteCloser{Writer: outW}),
|
||||
},
|
||||
},
|
||||
}, "", frontend, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
m, err := testutil.ReadTarToMap(outW.Bytes(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
for name := range m {
|
||||
require.Falsef(t, strings.HasPrefix(name, "../") ||
|
||||
strings.HasPrefix(name, `..\`) ||
|
||||
strings.Contains(name, `/../`) ||
|
||||
strings.Contains(name, `\..\`) ||
|
||||
strings.Contains(name, `\`) ||
|
||||
strings.Contains(name, ":"),
|
||||
"tar exporter emitted unsafe platform path %q", name)
|
||||
}
|
||||
|
||||
tarPaths := make([]string, 0, len(m))
|
||||
for name := range m {
|
||||
tarPaths = append(tarPaths, name)
|
||||
}
|
||||
sort.Strings(tarPaths)
|
||||
|
||||
expectedPath := path.Join(".._buildkit-outside", integration.UnixOrWindows("payload.txt", "Files/payload.txt"))
|
||||
item := m[expectedPath]
|
||||
require.NotNilf(t, item, "expected sanitized tar path %q in %v", expectedPath, tarPaths)
|
||||
require.Equal(t, "payload", string(item.Data))
|
||||
}
|
||||
|
||||
func testExporterTargetExists(t *testing.T, sb integration.Sandbox) {
|
||||
workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter)
|
||||
c, err := New(sb.Context(), sb.Address())
|
||||
|
||||
@@ -89,6 +89,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
|
||||
testExportLocalModeDeleteMultiPlatformKeepsAllPlatforms,
|
||||
testExportLocalNoPlatformSplit,
|
||||
testExportLocalNoPlatformSplitOverwrite,
|
||||
testExportTarPlatformIDSanitized,
|
||||
testExporterTargetExists,
|
||||
testMultipleExporters,
|
||||
testSessionExporter,
|
||||
|
||||
@@ -3,7 +3,6 @@ package local
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -127,7 +126,7 @@ func (e *localExporterInstance) Export(ctx context.Context, inp *exporter.Source
|
||||
platformDirStat := func(k string, opt CreateFSOpts) *fstypes.Stat {
|
||||
st := &fstypes.Stat{
|
||||
Mode: uint32(os.ModeDir | 0755),
|
||||
Path: strings.ReplaceAll(k, "/", "_"),
|
||||
Path: PlatformIDToPath(k),
|
||||
}
|
||||
if opt.Epoch != nil && opt.Epoch.Value != nil {
|
||||
st.ModTime = opt.Epoch.Value.UnixNano()
|
||||
|
||||
@@ -199,7 +199,7 @@ func CreateFS(ctx context.Context, sessionID string, k string, ref cache.Immutab
|
||||
if addPlatformToFilename {
|
||||
nameExt := path.Ext(name)
|
||||
namBase := strings.TrimSuffix(name, nameExt)
|
||||
name = fmt.Sprintf("%s.%s%s", namBase, strings.ReplaceAll(k, "/", "_"), nameExt)
|
||||
name = fmt.Sprintf("%s.%s%s", namBase, PlatformIDToPath(k), nameExt)
|
||||
}
|
||||
if _, ok := names[name]; ok {
|
||||
return nil, nil, errors.Errorf("duplicate attestation path name %s", name)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package local
|
||||
|
||||
import "strings"
|
||||
|
||||
// PlatformIDToPath maps an exporter platform ID to a single path component.
|
||||
func PlatformIDToPath(id string) string {
|
||||
id = strings.NewReplacer("/", "_", `\`, "_", ":", "_").Replace(id)
|
||||
switch id {
|
||||
case ".", "..":
|
||||
return strings.Repeat("_", len(id))
|
||||
default:
|
||||
return id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPlatformIDToPath(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
id string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "slashes",
|
||||
id: "linux/amd64",
|
||||
want: "linux_amd64",
|
||||
},
|
||||
{
|
||||
name: "windows separators",
|
||||
id: `..\buildkit-outside`,
|
||||
want: ".._buildkit-outside",
|
||||
},
|
||||
{
|
||||
name: "drive separator",
|
||||
id: `windows/amd64:C`,
|
||||
want: "windows_amd64_C",
|
||||
},
|
||||
{
|
||||
name: "dot",
|
||||
id: ".",
|
||||
want: "_",
|
||||
},
|
||||
{
|
||||
name: "dot dot",
|
||||
id: "..",
|
||||
want: "__",
|
||||
},
|
||||
{
|
||||
name: "embedded dots",
|
||||
id: "linux.v2/amd64",
|
||||
want: "linux.v2_amd64",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.want, PlatformIDToPath(tc.id))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/moby/buildkit/cache"
|
||||
@@ -109,7 +108,7 @@ func (e *localExporterInstance) Export(ctx context.Context, inp *exporter.Source
|
||||
|
||||
st := &fstypes.Stat{
|
||||
Mode: uint32(os.ModeDir | 0755),
|
||||
Path: strings.ReplaceAll(k, "/", "_"),
|
||||
Path: local.PlatformIDToPath(k),
|
||||
}
|
||||
if opt.Epoch != nil && opt.Epoch.Value != nil {
|
||||
st.ModTime = opt.Epoch.Value.UnixNano()
|
||||
|
||||
Reference in new issue
Block a user