Permalink
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...
1 parent 4e90d92 commit 4c2d556335401a73fb536f17a9f7568ae59656d2 @zyga committed May 11, 2017
Showing with 239 additions and 0 deletions.
  1. +127 −0 interfaces/seccomp/parser.go
  2. +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

Please sign in to comment.