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