Skip to content

Commit 08d51a8

Browse files
authored
fix(misconf): handle unsupported experimental flags in Dockerfile (#9769)
Signed-off-by: nikpivkin <[email protected]>
1 parent 09ea608 commit 08d51a8

File tree

2 files changed

+108
-4
lines changed

2 files changed

+108
-4
lines changed

pkg/iac/scanners/dockerfile/parser/parser.go

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,21 @@ package parser
22

33
import (
44
"context"
5-
"fmt"
65
"io"
6+
"slices"
77
"strings"
88

99
"github.com/moby/buildkit/frontend/dockerfile/instructions"
1010
"github.com/moby/buildkit/frontend/dockerfile/parser"
11+
"golang.org/x/xerrors"
1112

1213
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
1314
)
1415

1516
func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfile, error) {
1617
parsed, err := parser.Parse(r)
1718
if err != nil {
18-
return nil, fmt.Errorf("dockerfile parse error: %w", err)
19+
return nil, xerrors.Errorf("dockerfile parse error: %w", err)
1920
}
2021

2122
var (
@@ -28,9 +29,9 @@ func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfil
2829
for _, child := range parsed.AST.Children {
2930
child.Value = strings.ToLower(child.Value)
3031

31-
instr, err := instructions.ParseInstruction(child)
32+
instr, err := parseInstruction(child)
3233
if err != nil {
33-
return nil, fmt.Errorf("parse dockerfile instruction: %w", err)
34+
return nil, xerrors.Errorf("parse dockerfile instruction: %w", err)
3435
}
3536

3637
if _, ok := instr.(*instructions.Stage); ok {
@@ -89,6 +90,39 @@ func Parse(_ context.Context, r io.Reader, path string) ([]*dockerfile.Dockerfil
8990
return []*dockerfile.Dockerfile{&parsedFile}, nil
9091
}
9192

93+
func parseInstruction(child *parser.Node) (any, error) {
94+
for {
95+
instr, err := instructions.ParseInstruction(child)
96+
if err == nil {
97+
return instr, nil
98+
}
99+
100+
flagName := extractUnknownFlag(err.Error())
101+
if flagName == "" {
102+
return nil, xerrors.Errorf("parse instruction %q: %w", child.Value, err)
103+
}
104+
105+
filtered := slices.DeleteFunc(child.Flags, func(flag string) bool {
106+
return strings.HasPrefix(flag, flagName)
107+
})
108+
109+
if len(filtered) == len(child.Flags) {
110+
return nil, xerrors.Errorf("cannot remove unknown flag %q from flags %v", flagName, child.Flags)
111+
}
112+
child.Flags = filtered
113+
}
114+
}
115+
116+
func extractUnknownFlag(errMsg string) string {
117+
after, ok := strings.CutPrefix(errMsg, "unknown flag: ")
118+
if !ok {
119+
return ""
120+
}
121+
122+
flagName, _, _ := strings.Cut(after, " ")
123+
return flagName
124+
}
125+
92126
func originalFromHeredoc(node *parser.Node) string {
93127
var sb strings.Builder
94128
sb.WriteString(node.Original)

pkg/iac/scanners/dockerfile/parser/parser_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/stretchr/testify/assert"
88
"github.com/stretchr/testify/require"
99

10+
"github.com/aquasecurity/trivy/pkg/iac/providers/dockerfile"
1011
"github.com/aquasecurity/trivy/pkg/iac/scanners/dockerfile/parser"
1112
)
1213

@@ -57,6 +58,75 @@ CMD python /app/app.py
5758
assert.Equal(t, 4, commands[3].EndLine)
5859
}
5960

61+
func TestParserUnknownFlags(t *testing.T) {
62+
input := `FROM ubuntu:18.04
63+
COPY --foo --chown=1 --bar=./test . /app
64+
RUN --baz --baz --network=host make /app
65+
ONBUILD RUN --foo make /app
66+
CMD python /app/app.py
67+
`
68+
dfs, err := parser.Parse(t.Context(), strings.NewReader(input), "Dockerfile")
69+
require.NoError(t, err)
70+
require.Len(t, dfs, 1)
71+
72+
expected := &dockerfile.Dockerfile{
73+
Stages: []dockerfile.Stage{
74+
{
75+
Name: "ubuntu:18.04",
76+
Commands: []dockerfile.Command{
77+
{
78+
Cmd: "from",
79+
Value: []string{"ubuntu:18.04"},
80+
Flags: []string{},
81+
Original: "FROM ubuntu:18.04",
82+
Path: "Dockerfile",
83+
StartLine: 1,
84+
EndLine: 1,
85+
},
86+
{
87+
Cmd: "copy",
88+
Value: []string{".", "/app"},
89+
Flags: []string{"--chown=1"},
90+
Original: "COPY --foo --chown=1 --bar=./test . /app",
91+
Path: "Dockerfile",
92+
StartLine: 2,
93+
EndLine: 2,
94+
},
95+
{
96+
Cmd: "run",
97+
Value: []string{"make /app"},
98+
Flags: []string{"--network=host"},
99+
Original: "RUN --baz --baz --network=host make /app",
100+
Path: "Dockerfile",
101+
StartLine: 3,
102+
EndLine: 3,
103+
},
104+
{
105+
Cmd: "onbuild",
106+
SubCmd: "RUN",
107+
Value: []string{"make /app"},
108+
Flags: []string{},
109+
Original: "ONBUILD RUN --foo make /app",
110+
Path: "Dockerfile",
111+
StartLine: 4,
112+
EndLine: 4,
113+
},
114+
{
115+
Cmd: "cmd",
116+
Value: []string{"python /app/app.py"},
117+
Flags: []string{},
118+
Original: "CMD python /app/app.py",
119+
Path: "Dockerfile",
120+
StartLine: 5,
121+
EndLine: 5,
122+
},
123+
},
124+
},
125+
},
126+
}
127+
assert.Equal(t, expected, dfs[0])
128+
}
129+
60130
func Test_ParseHeredocs(t *testing.T) {
61131
tests := []struct {
62132
name string

0 commit comments

Comments
 (0)