Skip to content

Commit

Permalink
Merge pull request #4601 from tonistiigi/0131-fix-validate-nil
Browse files Browse the repository at this point in the history
Add more validations for nil values
  • Loading branch information
tonistiigi authored Jan 31, 2024
2 parents 3436b4d + 5d7d85f commit 1981eb1
Show file tree
Hide file tree
Showing 12 changed files with 446 additions and 10 deletions.
5 changes: 4 additions & 1 deletion client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
}

func TestIntegration(t *testing.T) {
testIntegration(t, allTests...)
testIntegration(t, append(allTests, validationTests...)...)
}

func testIntegration(t *testing.T, funcs ...func(t *testing.T, sb integration.Sandbox)) {
Expand Down Expand Up @@ -8913,6 +8913,7 @@ cat <<EOF > $BUILDKIT_SCAN_DESTINATION/spdx.json
EOF
`
img.Config.Cmd = []string{"/bin/sh", "-c", cmd}
img.Platform = p
config, err := json.Marshal(img)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal image config")
Expand Down Expand Up @@ -9191,6 +9192,7 @@ cat <<EOF > $BUILDKIT_SCAN_DESTINATION/spdx.json
EOF
`
img.Config.Cmd = []string{"/bin/sh", "-c", cmd}
img.Platform = p
config, err := json.Marshal(img)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal image config")
Expand Down Expand Up @@ -9243,6 +9245,7 @@ EOF

var img ocispecs.Image
img.Config.Cmd = []string{"/bin/sh", "-c", "cat /greeting"}
img.Platform = p
config, err := json.Marshal(img)
if err != nil {
return nil, errors.Wrapf(err, "failed to marshal image config")
Expand Down
319 changes: 319 additions & 0 deletions client/validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
package client

import (
"context"
"encoding/json"
"io"
"testing"

"github.com/moby/buildkit/client/llb"
"github.com/moby/buildkit/exporter/containerimage/exptypes"
"github.com/moby/buildkit/frontend/gateway/client"
sppb "github.com/moby/buildkit/sourcepolicy/pb"
"github.com/moby/buildkit/util/testutil/integration"
"github.com/moby/buildkit/util/testutil/workers"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/require"
)

var validationTests = []func(t *testing.T, sb integration.Sandbox){
testValidateNullConfig,
testValidateInvalidConfig,
testValidatePlatformsEmpty,
testValidatePlatformsInvalid,
testValidateSourcePolicy,
}

func testValidateNullConfig(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter)

ctx := sb.Context()

c, err := New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()

b := func(ctx context.Context, c client.Client) (*client.Result, error) {
def, err := llb.Scratch().Marshal(ctx)
if err != nil {
return nil, err
}

res, err := c.Solve(ctx, client.SolveRequest{
Evaluate: true,
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}
res.AddMeta("containerimage.config", []byte("null"))
return res, nil
}

_, err = c.Build(ctx, SolveOpt{
Exports: []ExportEntry{
{
Type: ExporterOCI,
Output: fixedWriteCloser(nopWriteCloser{io.Discard}),
},
},
}, "", b, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid null image config for export")
}

func testValidateInvalidConfig(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter)

ctx := sb.Context()

c, err := New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()

b := func(ctx context.Context, c client.Client) (*client.Result, error) {
def, err := llb.Scratch().Marshal(ctx)
if err != nil {
return nil, err
}

res, err := c.Solve(ctx, client.SolveRequest{
Evaluate: true,
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}
var img ocispecs.Image
img.Platform = ocispecs.Platform{
Architecture: "amd64",
}
dt, err := json.Marshal(img)
if err != nil {
return nil, err
}
res.AddMeta("containerimage.config", dt)
return res, nil
}

_, err = c.Build(ctx, SolveOpt{
Exports: []ExportEntry{
{
Type: ExporterOCI,
Output: fixedWriteCloser(nopWriteCloser{io.Discard}),
},
},
}, "", b, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid image config: os and architecture must be specified together")
}

func testValidatePlatformsEmpty(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter)

ctx := sb.Context()

c, err := New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()

b := func(ctx context.Context, c client.Client) (*client.Result, error) {
def, err := llb.Scratch().Marshal(ctx)
if err != nil {
return nil, err
}

res, err := c.Solve(ctx, client.SolveRequest{
Evaluate: true,
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}
res.AddMeta("refs.platforms", []byte("null"))
return res, nil
}

_, err = c.Build(ctx, SolveOpt{
Exports: []ExportEntry{
{
Type: ExporterOCI,
Output: fixedWriteCloser(nopWriteCloser{io.Discard}),
},
},
}, "", b, nil)
require.Error(t, err)
require.Contains(t, err.Error(), "invalid empty platforms index for exporter")
}

func testValidatePlatformsInvalid(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)
workers.CheckFeatureCompat(t, sb, workers.FeatureOCIExporter)

ctx := sb.Context()

c, err := New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()

tcases := []struct {
name string
value []exptypes.Platform
exp string
}{
{
name: "emptyID",
value: []exptypes.Platform{{}},
exp: "invalid empty platform key for exporter",
},
{
name: "missingOS",
value: []exptypes.Platform{
{
ID: "foo",
},
},
exp: "invalid platform value",
},
}

for _, tc := range tcases {
t.Run(tc.name, func(t *testing.T) {
b := func(ctx context.Context, c client.Client) (*client.Result, error) {
def, err := llb.Scratch().Marshal(ctx)
if err != nil {
return nil, err
}

res, err := c.Solve(ctx, client.SolveRequest{
Evaluate: true,
Definition: def.ToPB(),
})
if err != nil {
return nil, err
}

dt, err := json.Marshal(exptypes.Platforms{Platforms: tc.value})
if err != nil {
return nil, err
}

res.AddMeta("refs.platforms", dt)
return res, nil
}

_, err = c.Build(ctx, SolveOpt{
Exports: []ExportEntry{
{
Type: ExporterOCI,
Output: fixedWriteCloser(nopWriteCloser{io.Discard}),
},
},
}, "", b, nil)
require.Error(t, err)
require.Contains(t, err.Error(), tc.exp)
})
}
}

func testValidateSourcePolicy(t *testing.T, sb integration.Sandbox) {
requiresLinux(t)

ctx := sb.Context()

c, err := New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()

tcases := []struct {
name string
value *sppb.Policy
exp string
}{
// this condition fails on marshaling atm
// {
// name: "nilrule",
// value: &sppb.Policy{
// Rules: []*sppb.Rule{nil},
// },
// exp: "",
// },
{
name: "nilselector",
value: &sppb.Policy{
Rules: []*sppb.Rule{
{
Action: sppb.PolicyAction_CONVERT,
},
},
},
exp: "invalid nil selector in policy",
},
{
name: "emptyaction",
value: &sppb.Policy{
Rules: []*sppb.Rule{
{
Action: sppb.PolicyAction(9000),
Selector: &sppb.Selector{
Identifier: "docker-image://docker.io/library/alpine:latest",
},
},
},
},
exp: "unknown type",
},
{
name: "nilupdates",
value: &sppb.Policy{
Rules: []*sppb.Rule{
{
Action: sppb.PolicyAction_CONVERT,
Selector: &sppb.Selector{
Identifier: "docker-image://docker.io/library/alpine:latest",
},
},
},
},
exp: "missing destination for convert rule",
},
}

for _, tc := range tcases {
t.Run(tc.name, func(t *testing.T) {
var viaFrontend bool

b := func(ctx context.Context, c client.Client) (*client.Result, error) {
def, err := llb.Image("alpine").Marshal(ctx)
if err != nil {
return nil, err
}

req := client.SolveRequest{
Evaluate: true,
Definition: def.ToPB(),
}
if viaFrontend {
req.SourcePolicies = []*sppb.Policy{
tc.value,
}
}
return c.Solve(ctx, req)
}

_, err = c.Build(ctx, SolveOpt{
SourcePolicy: tc.value,
}, "", b, nil)
require.Error(t, err)
require.Contains(t, err.Error(), tc.exp)

viaFrontend = true
_, err = c.Build(ctx, SolveOpt{}, "", b, nil)
require.Error(t, err)
require.Contains(t, err.Error(), tc.exp)
})
}
}
3 changes: 3 additions & 0 deletions control/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ func (c *Controller) Solve(ctx context.Context, req *controlapi.SolveRequest) (*

var cacheImports []frontend.CacheOptionsEntry
for _, im := range req.Cache.Imports {
if im == nil {
continue
}
cacheImports = append(cacheImports, frontend.CacheOptionsEntry{
Type: im.Type,
Attrs: im.Attrs,
Expand Down
14 changes: 14 additions & 0 deletions exporter/containerimage/exptypes/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ func ParsePlatforms(meta map[string][]byte) (Platforms, error) {
return Platforms{}, errors.Wrapf(err, "failed to parse platforms passed to provenance processor")
}
}
if len(ps.Platforms) == 0 {
return Platforms{}, errors.Errorf("invalid empty platforms index for exporter")
}
for i, p := range ps.Platforms {
if p.ID == "" {
return Platforms{}, errors.Errorf("invalid empty platform key for exporter")
}
if p.Platform.OS == "" || p.Platform.Architecture == "" {
return Platforms{}, errors.Errorf("invalid platform value %v for exporter", p.Platform)
}
ps.Platforms[i].Platform = platforms.Normalize(p.Platform)
}
return ps, nil
}

Expand All @@ -36,6 +48,8 @@ func ParsePlatforms(meta map[string][]byte) (Platforms, error) {
OSFeatures: img.OSFeatures,
Variant: img.Variant,
}
} else if img.OS != "" || img.Architecture != "" {
return Platforms{}, errors.Errorf("invalid image config: os and architecture must be specified together")
}
}
p = platforms.Normalize(p)
Expand Down
Loading

0 comments on commit 1981eb1

Please sign in to comment.