Permalink
Please sign in to comment.
Browse files
interfaces/seccomp: add parser for seccomp snippets
This patch adds an parser for seccomp snippets so that they can be converted to the fully typed variant. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com>
- Loading branch information...
Showing
with
239 additions
and 0 deletions.
- +127 −0 interfaces/seccomp/parser.go
- +112 −0 interfaces/seccomp/parser_test.go
| @@ -0,0 +1,127 @@ | ||
| +// -*- Mode: Go; indent-tabs-mode: t -*- | ||
| + | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
| +package seccomp | ||
| + | ||
| +import ( | ||
| + "bufio" | ||
| + "fmt" | ||
| + "strconv" | ||
| + "strings" | ||
| + | ||
| + "github.com/snapcore/snapd/logger" | ||
| +) | ||
| + | ||
| +// ParseSnippet parses a seccomp filtering snippet into a sequence of rules. | ||
| +func ParseSnippet(snippet string) ([]Rule, error) { | ||
| + var rules []Rule | ||
| + var comments []string | ||
| + | ||
| + scanner := bufio.NewScanner(strings.NewReader(snippet)) | ||
| + for scanner.Scan() { | ||
| + s := scanner.Text() | ||
| + if i := strings.IndexRune(s, '#'); i != -1 { | ||
| + comments = append(comments, s[i:]) | ||
| + s = s[:i] | ||
| + if s == "" { | ||
| + continue | ||
| + } | ||
| + } | ||
| + fields := strings.Fields(strings.TrimSpace(s)) | ||
| + if len(fields) == 0 { | ||
| + // Ignore whitespace but collect it as a comment. | ||
| + comments = append(comments, s) | ||
| + continue | ||
| + } | ||
| + var args []ArgConstraint | ||
| + for _, field := range fields[1:] { | ||
| + var op ConstraintOp | ||
| + var value string | ||
| + switch { | ||
| + case field == "-": | ||
| + op = Any | ||
| + case strings.HasPrefix(field, "!"): | ||
| + op = NotEqual | ||
| + value = field[1:] | ||
| + case strings.HasPrefix(field, ">="): | ||
| + op = GreaterEqual | ||
| + value = field[2:] | ||
| + case strings.HasPrefix(field, "<="): | ||
| + op = LessEqual | ||
| + value = field[2:] | ||
| + case strings.HasPrefix(field, ">"): | ||
| + op = Greater | ||
| + value = field[1:] | ||
| + case strings.HasPrefix(field, "<"): | ||
| + op = Less | ||
| + value = field[1:] | ||
| + case strings.HasPrefix(field, "|"): | ||
| + op = Mask | ||
| + value = field[1:] | ||
| + default: | ||
| + op = Equal | ||
| + value = field | ||
| + } | ||
| + arg := ArgConstraint{Op: op} | ||
| + if op != Any { | ||
| + if value == "" { | ||
| + return nil, fmt.Errorf("cannot parse seccomp rule %q: expected value after operator %s", s, op) | ||
| + } | ||
| + arg.Value = value | ||
| + // Parse the value. This handles numeric literals and known symbolic constants. | ||
| + // Unparsed things are kept as-is for snap-confine to resolve. | ||
| + if resolvedValue, err := parseValue(value); err == nil { | ||
| + arg.ResolvedValue = resolvedValue | ||
| + arg.IsResolved = true | ||
| + } else { | ||
| + // Be noisy about errors while parsing. | ||
| + logger.Noticef("%s", err) | ||
| + } | ||
| + } | ||
| + args = append(args, arg) | ||
| + } | ||
| + rule := Rule{ | ||
| + Comment: strings.Join(comments, "\n"), | ||
| + SysCall: SysCall(fields[0]), | ||
| + Args: args, | ||
| + } | ||
| + comments = nil | ||
| + rules = append(rules, rule) | ||
| + } | ||
| + if err := scanner.Err(); err != nil { | ||
| + return nil, err | ||
| + } | ||
| + if len(comments) != 0 { | ||
| + justCommentRule := Rule{Comment: strings.Join(comments, "\n")} | ||
| + rules = append(rules, justCommentRule) | ||
| + } | ||
| + return rules, nil | ||
| +} | ||
| + | ||
| +func parseValue(s string) (int, error) { | ||
| + // value may be an interger literal. | ||
| + if value, err := strconv.Atoi(s); err == nil { | ||
| + return value, nil | ||
| + } | ||
| + // value may be a known symbolic constant. | ||
| + if value, ok := knownConstants[s]; ok { | ||
| + return value, nil | ||
| + } | ||
| + return 0, fmt.Errorf("unknown symbolic seccomp argment value %q", s) | ||
| +} |
| @@ -0,0 +1,112 @@ | ||
| +// -*- Mode: Go; indent-tabs-mode: t -*- | ||
| + | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
| +package seccomp_test | ||
| + | ||
| +import ( | ||
| + "syscall" | ||
| + | ||
| + . "gopkg.in/check.v1" | ||
| + | ||
| + "github.com/snapcore/snapd/interfaces/seccomp" | ||
| +) | ||
| + | ||
| +type parserSuite struct{} | ||
| + | ||
| +var _ = Suite(&parserSuite{}) | ||
| + | ||
| +// The result may be empty list of rules. | ||
| +func (s *parserSuite) TestParse0(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, HasLen, 0) | ||
| +} | ||
| + | ||
| +// Trivial rule is parsed correctly. | ||
| +func (s *parserSuite) TestParse1(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("bind") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, DeepEquals, []seccomp.Rule{{SysCall: seccomp.SysBind}}) | ||
| +} | ||
| + | ||
| +// Rule can have a trailing comment on the same line. | ||
| +func (s *parserSuite) TestParse2(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("bind # bind is nice") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, DeepEquals, []seccomp.Rule{{Comment: "# bind is nice", SysCall: seccomp.SysBind}}) | ||
| +} | ||
| + | ||
| +// Comments can precede a rule. | ||
| +func (s *parserSuite) TestParse3(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("# bind is nice\nbind\n") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, DeepEquals, []seccomp.Rule{{Comment: "# bind is nice", SysCall: seccomp.SysBind}}) | ||
| +} | ||
| + | ||
| +// Multi-line comments are aggregated correctly. | ||
| +func (s *parserSuite) TestParse4(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("# bind is nice\n# bind is very very nice!\nbind\n") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, DeepEquals, []seccomp.Rule{{Comment: "# bind is nice\n# bind is very very nice!", SysCall: seccomp.SysBind}}) | ||
| +} | ||
| + | ||
| +// Filtering can be done on numeric arguments. | ||
| +func (s *parserSuite) TestParse5(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("fchown - 0 42") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, DeepEquals, []seccomp.Rule{{ | ||
| + SysCall: seccomp.SysFchown, | ||
| + Args: []seccomp.ArgConstraint{ | ||
| + {Op: seccomp.Any}, | ||
| + {Op: seccomp.Equal, Value: "0", ResolvedValue: 0, IsResolved: true}, | ||
| + {Op: seccomp.Equal, Value: "42", ResolvedValue: 42, IsResolved: true}, | ||
| + }, | ||
| + }}) | ||
| +} | ||
| + | ||
| +// Filtering can be done on symbolic arguments. | ||
| +func (s *parserSuite) TestParse6(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("socket AF_NETLINK - NETLINK_AUDIT") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, DeepEquals, []seccomp.Rule{{ | ||
| + SysCall: seccomp.SysSocket, | ||
| + Args: []seccomp.ArgConstraint{ | ||
| + {Op: seccomp.Equal, Value: "AF_NETLINK", ResolvedValue: syscall.AF_NETLINK, IsResolved: true}, | ||
| + {Op: seccomp.Any}, | ||
| + {Op: seccomp.Equal, Value: "NETLINK_AUDIT", ResolvedValue: syscall.NETLINK_AUDIT, IsResolved: true}, | ||
| + }, | ||
| + }}) | ||
| +} | ||
| + | ||
| +// Multiple rules can be returned | ||
| +func (s *parserSuite) TestParse7(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("socket\nbind") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, DeepEquals, []seccomp.Rule{ | ||
| + {SysCall: seccomp.SysSocket}, | ||
| + {SysCall: seccomp.SysBind}, | ||
| + }) | ||
| +} | ||
| + | ||
| +// Pure comment rules can be used for compatibility with commented-out snippets. | ||
| +func (s *parserSuite) TestParse8(c *C) { | ||
| + rules, err := seccomp.ParseSnippet("# just comment") | ||
| + c.Assert(err, IsNil) | ||
| + c.Assert(rules, DeepEquals, []seccomp.Rule{{Comment: "# just comment"}}) | ||
| +} |
0 comments on commit
4c2d556