github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/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.Output() 76 if err != nil { 77 if exitErr, ok := err.(*exec.ExitError); ok && len(exitErr.Stderr) > 0 { 78 output = exitErr.Stderr 79 } 80 return "", osutil.OutputErr(output, err) 81 } 82 raw := bytes.TrimSpace(output) 83 // Example valid output: 84 // 7ac348ac9c934269214b00d1692dfa50d5d4a157 2.3.3 03e996919907bc7163bc83b95bca0ecab31300f20dfa365ea14047c698340e7c bpf-actlog 85 if match := validVersionInfo.Match(raw); !match { 86 return "", fmt.Errorf("invalid format of version-info: %q", raw) 87 } 88 89 return VersionInfo(raw), nil 90 } 91 92 var compilerVersionInfoImpl = func(lookupTool func(name string) (string, error)) (VersionInfo, error) { 93 c, err := NewCompiler(lookupTool) 94 if err != nil { 95 return VersionInfo(""), err 96 } 97 return c.VersionInfo() 98 } 99 100 // CompilerVersionInfo returns the version information of snap-seccomp 101 // looked up via lookupTool. 102 func CompilerVersionInfo(lookupTool func(name string) (string, error)) (VersionInfo, error) { 103 return compilerVersionInfoImpl(lookupTool) 104 } 105 106 // MockCompilerVersionInfo mocks the return value of CompilerVersionInfo. 107 func MockCompilerVersionInfo(versionInfo string) (restore func()) { 108 old := compilerVersionInfoImpl 109 compilerVersionInfoImpl = func(_ func(name string) (string, error)) (VersionInfo, error) { 110 return VersionInfo(versionInfo), nil 111 } 112 return func() { 113 compilerVersionInfoImpl = old 114 } 115 } 116 117 var errEmptyVersionInfo = errors.New("empty version-info") 118 119 // VersionInfo represents information about the seccomp compiler 120 type VersionInfo string 121 122 // LibseccompVersion parses VersionInfo and provides the libseccomp version 123 func (vi VersionInfo) LibseccompVersion() (string, error) { 124 if vi == "" { 125 return "", errEmptyVersionInfo 126 } 127 if match := validVersionInfo.Match([]byte(vi)); !match { 128 return "", fmt.Errorf("invalid format of version-info: %q", vi) 129 } 130 return strings.Split(string(vi), " ")[1], nil 131 } 132 133 // Features parses the output of VersionInfo and provides the 134 // golang seccomp features 135 func (vi VersionInfo) Features() (string, error) { 136 if vi == "" { 137 return "", errEmptyVersionInfo 138 } 139 if match := validVersionInfo.Match([]byte(vi)); !match { 140 return "", fmt.Errorf("invalid format of version-info: %q", vi) 141 } 142 return strings.Split(string(vi), " ")[3], nil 143 } 144 145 // HasFeature parses the output of VersionInfo and answers whether or 146 // not golang-seccomp supports the feature 147 func (vi VersionInfo) HasFeature(feature string) (bool, error) { 148 features, err := vi.Features() 149 if err != nil { 150 return false, err 151 } 152 for _, f := range strings.Split(features, ":") { 153 if f == feature { 154 return true, nil 155 } 156 } 157 return false, nil 158 } 159 160 // BuildTimeRequirementError represents the error case of a feature 161 // that cannot be supported because of unfulfilled build time 162 // requirements. 163 type BuildTimeRequirementError struct { 164 Feature string 165 Requirements []string 166 } 167 168 func (e *BuildTimeRequirementError) RequirementsString() string { 169 return strings.Join(e.Requirements, ", ") 170 } 171 172 func (e *BuildTimeRequirementError) Error() string { 173 return fmt.Sprintf("%s requires a snapd built against %s", e.Feature, e.RequirementsString()) 174 } 175 176 // SupportsRobustArgumentFiltering parses the output of VersionInfo and 177 // determines if libseccomp and golang-seccomp are new enough to support robust 178 // argument filtering 179 func (vi VersionInfo) SupportsRobustArgumentFiltering() error { 180 libseccompVersion, err := vi.LibseccompVersion() 181 if err != nil { 182 return err 183 } 184 185 // Parse <libseccomp version> 186 tmp := strings.Split(libseccompVersion, ".") 187 maj, err := strconv.Atoi(tmp[0]) 188 if err != nil { 189 return fmt.Errorf("cannot obtain seccomp compiler information: %v", err) 190 } 191 min, err := strconv.Atoi(tmp[1]) 192 if err != nil { 193 return fmt.Errorf("cannot obtain seccomp compiler information: %v", err) 194 } 195 196 var unfulfilledReqs []string 197 198 // libseccomp < 2.4 has significant argument filtering bugs that we 199 // cannot reliably work around with this feature. 200 if maj < 2 || (maj == 2 && min < 4) { 201 unfulfilledReqs = append(unfulfilledReqs, "libseccomp >= 2.4") 202 } 203 204 // Due to https://github.com/seccomp/libseccomp-golang/issues/22, 205 // golang-seccomp <= 0.9.0 cannot create correct BPFs for this feature. 206 // The package does not contain any version information, but we know 207 // that ActLog was implemented in the library after this issue was 208 // fixed, so base the decision on that. ActLog is first available in 209 // 0.9.1. 210 res, err := vi.HasFeature("bpf-actlog") 211 if err != nil { 212 return err 213 } 214 if !res { 215 unfulfilledReqs = append(unfulfilledReqs, "golang-seccomp >= 0.9.1") 216 } 217 218 if len(unfulfilledReqs) != 0 { 219 return &BuildTimeRequirementError{ 220 Feature: "robust argument filtering", 221 Requirements: unfulfilledReqs, 222 } 223 } 224 225 return nil 226 } 227 228 // Compile compiles given source profile and saves the result to the out 229 // location. 230 func (c *Compiler) Compile(in, out string) error { 231 cmd := exec.Command(c.snapSeccomp, "compile", in, out) 232 if output, err := cmd.CombinedOutput(); err != nil { 233 return osutil.OutputErr(output, err) 234 } 235 return nil 236 }