Files
buildkit/client/llb/exec_test.go
Tonis Tiigi 64bbec89ca security: validate exec security modes
Reject unknown SecurityMode values before generating executor specs. Ensure
only SecurityMode_INSECURE takes the insecure path, while validated non-insecure
modes keep sandbox security options. Add gateway, raw LLB, and LLB marshal
regression coverage for invalid enum values.

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 972895718f963c71388aeebb7cff423ef6963a92)
2026-06-24 10:56:46 -07:00

175 lines
4.9 KiB
Go

package llb
import (
"context"
"testing"
"github.com/moby/buildkit/solver/pb"
"github.com/stretchr/testify/require"
)
func TestTmpfsMountError(t *testing.T) {
t.Parallel()
st := Image("foo").Run(Shlex("args")).AddMount("/tmp", Scratch(), Tmpfs())
_, err := st.Marshal(context.TODO())
require.Error(t, err)
require.Contains(t, err.Error(), "can't be used as a parent")
st = Image("foo").Run(Shlex("args"), AddMount("/tmp", Scratch(), Tmpfs())).Root()
_, err = st.Marshal(context.TODO())
require.NoError(t, err)
st = Image("foo").Run(Shlex("args"), AddMount("/tmp", Image("bar"), Tmpfs())).Root()
_, err = st.Marshal(context.TODO())
require.Error(t, err)
require.Contains(t, err.Error(), "must use scratch")
}
func TestInvalidSecurityModeMarshalError(t *testing.T) {
t.Parallel()
st := Image("busybox:latest").
Run(Shlex("true"), Security(pb.SecurityMode(2))).Root()
_, err := st.Marshal(context.TODO())
require.Error(t, err)
require.Contains(t, err.Error(), "invalid security mode")
}
func TestValidGetMountIndex(t *testing.T) {
// tests for https://github.com/moby/buildkit/issues/1520
// tmpfs mount /c will sort later than target mount /b, /b will have output index==1
st := Image("foo").Run(Shlex("args"), AddMount("/b", Scratch()), AddMount("/c", Scratch(), Tmpfs())).GetMount("/b")
mountOutput, ok := st.Output().(*output)
require.True(t, ok, "mount output is expected type")
mountIndex, err := mountOutput.getIndex()
require.NoError(t, err, "failed to getIndex")
require.Equal(t, pb.OutputIndex(1), mountIndex, "unexpected mount index")
// now swapping so the tmpfs mount /a will sort earlier than the target mount /b, /b should still have output index==1
st = Image("foo").Run(Shlex("args"), AddMount("/b", Scratch()), AddMount("/a", Scratch(), Tmpfs())).GetMount("/b")
mountOutput, ok = st.Output().(*output)
require.True(t, ok, "mount output is expected type")
mountIndex, err = mountOutput.getIndex()
require.NoError(t, err, "failed to getIndex")
require.Equal(t, pb.OutputIndex(1), mountIndex, "unexpected mount index")
}
func TestLinuxResourcesMarshal(t *testing.T) {
t.Parallel()
st := Image("busybox:latest").
Run(
Shlex("true"),
MemoryLimit(64*1024*1024),
CPUShares(512),
CPUQuota(50000),
CPUPeriod(100000),
CpusetCpus("0-3"),
CpusetMems("0"),
).Root()
def, err := st.Marshal(context.TODO())
require.NoError(t, err)
// Resources should be in OpMetadata (not in the Op bytes / cache key)
var found bool
for _, md := range def.Metadata {
if md.LinuxResources == nil {
continue
}
found = true
res := md.LinuxResources
require.Equal(t, int64(64*1024*1024), res.Memory)
require.Equal(t, uint64(512), res.CpuShares)
require.Equal(t, int64(50000), res.CpuQuota)
require.Equal(t, uint64(100000), res.CpuPeriod)
require.Equal(t, "0-3", res.CpusetCpus)
require.Equal(t, "0", res.CpusetMems)
}
require.True(t, found, "LinuxResources not found in OpMetadata")
}
func TestLinuxResourcesNotInCacheKey(t *testing.T) {
t.Parallel()
// Two ops with same command but different resource limits must produce the same digest
st1 := Image("busybox:latest").
Run(Shlex("echo hello"), MemoryLimit(64*1024*1024)).Root()
st2 := Image("busybox:latest").
Run(Shlex("echo hello"), MemoryLimit(128*1024*1024)).Root()
st3 := Image("busybox:latest").
Run(Shlex("echo hello")).Root()
def1, err := st1.Marshal(context.TODO())
require.NoError(t, err)
def2, err := st2.Marshal(context.TODO())
require.NoError(t, err)
def3, err := st3.Marshal(context.TODO())
require.NoError(t, err)
// All three should produce the same definition bytes (same cache key)
require.Equal(t, def1.Def, def2.Def, "different resource limits should produce same digest")
require.Equal(t, def1.Def, def3.Def, "resource limits vs no limits should produce same digest")
}
func TestLinuxResourcesMerge(t *testing.T) {
t.Parallel()
// Test that individual resource limit functions merge correctly
st := Image("busybox:latest").
Run(
Shlex("true"),
MemoryLimit(64*1024*1024),
CPUShares(512),
).Root()
def, err := st.Marshal(context.TODO())
require.NoError(t, err)
for _, md := range def.Metadata {
if md.LinuxResources == nil {
continue
}
res := md.LinuxResources
require.Equal(t, int64(64*1024*1024), res.Memory)
require.Equal(t, uint64(512), res.CpuShares)
// Unset fields should be zero
require.Equal(t, int64(0), res.CpuQuota)
require.Equal(t, uint64(0), res.CpuPeriod)
return
}
t.Fatal("LinuxResources not found in OpMetadata")
}
func TestExecOpMarshalConsistency(t *testing.T) {
var prevDef [][]byte
st := Image("busybox:latest").
Run(
Args([]string{"ls /a; ls /b"}),
AddMount("/b", Scratch().File(Mkfile("file1", 0644, []byte("file1 contents")))),
).AddMount("/a", Scratch().File(Mkfile("file2", 0644, []byte("file2 contents"))))
for range 100 {
def, err := st.Marshal(context.TODO())
require.NoError(t, err)
if prevDef != nil {
require.Equal(t, def.Def, prevDef)
}
prevDef = def.Def
}
}