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 }