github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/sandbox/seccomp/compiler.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package seccomp
    21  
    22  import (
    23  	"bytes"
    24  	"errors"
    25  	"fmt"
    26  	"os/exec"
    27  	"regexp"
    28  	"strconv"
    29  	"strings"
    30  
    31  	"github.com/snapcore/snapd/osutil"
    32  )
    33  
    34  var (
    35  	// version-info format: <build-id> <libseccomp-version> <hash>
    36  	// <features> Where, the hash is calculated over all syscall names
    37  	// supported by the libseccomp library. The build-id is a string of up
    38  	// to 166 chars, accommodates 128-bit MD5 (32 chars), 160-bit SHA-1 (40
    39  	// chars) generated by GNU ld, and 83-byte (166 chars) build ID
    40  	// generated by Go toolchain, also provides an upper limit of the
    41  	// user-settable build ID. The hash is a 256-bit SHA-256 (64 char)
    42  	// string. Allow libseccomp version to be 1-5 chars per field (eg, 1.2.3
    43  	// or 12345.23456.34567) and 1-30 chars of colon-separated features. Ex:
    44  	// 7ac348ac9c934269214b00d1692dfa50d5d4a157 2.3.3
    45  	// 03e996919907bc7163bc83b95bca0ecab31300f20dfa365ea14047c698340e7c
    46  	// bpf-actlog
    47  	validVersionInfo = regexp.MustCompile(`^[0-9a-f]{1,166} [0-9]{1,5}\.[0-9]{1,5}\.[0-9]{1,5} [0-9a-f]{1,64} [-a-z0-9:]{1,30}$`)
    48  )
    49  
    50  type Compiler struct {
    51  	snapSeccomp string
    52  }
    53  
    54  // NewCompiler returns a wrapper for the compiler binary. The path to the binary is
    55  // looked up using the lookupTool helper.
    56  func NewCompiler(lookupTool func(name string) (string, error)) (*Compiler, error) {
    57  	if lookupTool == nil {
    58  		panic("lookup tool func not provided")
    59  	}
    60  
    61  	path, err := lookupTool("snap-seccomp")
    62  	if err != nil {
    63  		return nil, err
    64  	}
    65  
    66  	return &Compiler{snapSeccomp: path}, nil
    67  }
    68  
    69  // VersionInfo returns the version information of the compiler. The format of
    70  // version information is: <build-id> <libseccomp-version> <hash> <features>.
    71  // Where, the hash is calculated over all syscall names supported by the
    72  // libseccomp library.
    73  func (c *Compiler) VersionInfo() (VersionInfo, error) {
    74  	cmd := exec.Command(c.snapSeccomp, "version-info")
    75  	output, err := cmd.CombinedOutput()
    76  	if err != nil {
    77  		return "", osutil.OutputErr(output, err)
    78  	}
    79  	raw := bytes.TrimSpace(output)
    80  	// Example valid output:
    81  	// 7ac348ac9c934269214b00d1692dfa50d5d4a157 2.3.3 03e996919907bc7163bc83b95bca0ecab31300f20dfa365ea14047c698340e7c bpf-actlog
    82  	if match := validVersionInfo.Match(raw); !match {
    83  		return "", fmt.Errorf("invalid format of version-info: %q", raw)
    84  	}
    85  
    86  	return VersionInfo(raw), nil
    87  }
    88  
    89  var compilerVersionInfoImpl = func(lookupTool func(name string) (string, error)) (VersionInfo, error) {
    90  	c, err := NewCompiler(lookupTool)
    91  	if err != nil {
    92  		return VersionInfo(""), err
    93  	}
    94  	return c.VersionInfo()
    95  }
    96  
    97  // CompilerVersionInfo returns the version information of snap-seccomp
    98  // looked up via lookupTool.
    99  func CompilerVersionInfo(lookupTool func(name string) (string, error)) (VersionInfo, error) {
   100  	return compilerVersionInfoImpl(lookupTool)
   101  }
   102  
   103  // MockCompilerVersionInfo mocks the return value of CompilerVersionInfo.
   104  func MockCompilerVersionInfo(versionInfo string) (restore func()) {
   105  	old := compilerVersionInfoImpl
   106  	compilerVersionInfoImpl = func(_ func(name string) (string, error)) (VersionInfo, error) {
   107  		return VersionInfo(versionInfo), nil
   108  	}
   109  	return func() {
   110  		compilerVersionInfoImpl = old
   111  	}
   112  }
   113  
   114  var errEmptyVersionInfo = errors.New("empty version-info")
   115  
   116  // VersionInfo represents information about the seccomp compiler
   117  type VersionInfo string
   118  
   119  // LibseccompVersion parses VersionInfo and provides the libseccomp version
   120  func (vi VersionInfo) LibseccompVersion() (string, error) {
   121  	if vi == "" {
   122  		return "", errEmptyVersionInfo
   123  	}
   124  	if match := validVersionInfo.Match([]byte(vi)); !match {
   125  		return "", fmt.Errorf("invalid format of version-info: %q", vi)
   126  	}
   127  	return strings.Split(string(vi), " ")[1], nil
   128  }
   129  
   130  // Features parses the output of VersionInfo and provides the
   131  // golang seccomp features
   132  func (vi VersionInfo) Features() (string, error) {
   133  	if vi == "" {
   134  		return "", errEmptyVersionInfo
   135  	}
   136  	if match := validVersionInfo.Match([]byte(vi)); !match {
   137  		return "", fmt.Errorf("invalid format of version-info: %q", vi)
   138  	}
   139  	return strings.Split(string(vi), " ")[3], nil
   140  }
   141  
   142  // HasFeature parses the output of VersionInfo and answers whether or
   143  // not golang-seccomp supports the feature
   144  func (vi VersionInfo) HasFeature(feature string) (bool, error) {
   145  	features, err := vi.Features()
   146  	if err != nil {
   147  		return false, err
   148  	}
   149  	for _, f := range strings.Split(features, ":") {
   150  		if f == feature {
   151  			return true, nil
   152  		}
   153  	}
   154  	return false, nil
   155  }
   156  
   157  // BuildTimeRequirementError represents the error case of a feature
   158  // that cannot be supported because of unfulfilled build time
   159  // requirements.
   160  type BuildTimeRequirementError struct {
   161  	Feature      string
   162  	Requirements []string
   163  }
   164  
   165  func (e *BuildTimeRequirementError) RequirementsString() string {
   166  	return strings.Join(e.Requirements, ", ")
   167  }
   168  
   169  func (e *BuildTimeRequirementError) Error() string {
   170  	return fmt.Sprintf("%s requires a snapd built against %s", e.Feature, e.RequirementsString())
   171  }
   172  
   173  // SupportsRobustArgumentFiltering parses the output of VersionInfo and
   174  // determines if libseccomp and golang-seccomp are new enough to support robust
   175  // argument filtering
   176  func (vi VersionInfo) SupportsRobustArgumentFiltering() error {
   177  	libseccompVersion, err := vi.LibseccompVersion()
   178  	if err != nil {
   179  		return err
   180  	}
   181  
   182  	// Parse <libseccomp version>
   183  	tmp := strings.Split(libseccompVersion, ".")
   184  	maj, err := strconv.Atoi(tmp[0])
   185  	if err != nil {
   186  		return fmt.Errorf("cannot obtain seccomp compiler information: %v", err)
   187  	}
   188  	min, err := strconv.Atoi(tmp[1])
   189  	if err != nil {
   190  		return fmt.Errorf("cannot obtain seccomp compiler information: %v", err)
   191  	}
   192  
   193  	var unfulfilledReqs []string
   194  
   195  	// libseccomp < 2.4 has significant argument filtering bugs that we
   196  	// cannot reliably work around with this feature.
   197  	if maj < 2 || (maj == 2 && min < 4) {
   198  		unfulfilledReqs = append(unfulfilledReqs, "libseccomp >= 2.4")
   199  	}
   200  
   201  	// Due to https://github.com/seccomp/libseccomp-golang/issues/22,
   202  	// golang-seccomp <= 0.9.0 cannot create correct BPFs for this feature.
   203  	// The package does not contain any version information, but we know
   204  	// that ActLog was implemented in the library after this issue was
   205  	// fixed, so base the decision on that. ActLog is first available in
   206  	// 0.9.1.
   207  	res, err := vi.HasFeature("bpf-actlog")
   208  	if err != nil {
   209  		return err
   210  	}
   211  	if !res {
   212  		unfulfilledReqs = append(unfulfilledReqs, "golang-seccomp >= 0.9.1")
   213  	}
   214  
   215  	if len(unfulfilledReqs) != 0 {
   216  		return &BuildTimeRequirementError{
   217  			Feature:      "robust argument filtering",
   218  			Requirements: unfulfilledReqs,
   219  		}
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  // Compile compiles given source profile and saves the result to the out
   226  // location.
   227  func (c *Compiler) Compile(in, out string) error {
   228  	cmd := exec.Command(c.snapSeccomp, "compile", in, out)
   229  	if output, err := cmd.CombinedOutput(); err != nil {
   230  		return osutil.OutputErr(output, err)
   231  	}
   232  	return nil
   233  }