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 }