gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/snapstate/check_snap.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2014-2016 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 snapstate 21 22 import ( 23 "fmt" 24 "regexp" 25 "strconv" 26 "strings" 27 28 "github.com/snapcore/snapd/arch" 29 "github.com/snapcore/snapd/asserts" 30 "github.com/snapcore/snapd/logger" 31 "github.com/snapcore/snapd/osutil" 32 "github.com/snapcore/snapd/overlord/snapstate/backend" 33 "github.com/snapcore/snapd/overlord/state" 34 "github.com/snapcore/snapd/release" 35 seccomp_compiler "github.com/snapcore/snapd/sandbox/seccomp" 36 "github.com/snapcore/snapd/snap" 37 "github.com/snapcore/snapd/snapdtool" 38 "github.com/snapcore/snapd/strutil" 39 ) 40 41 // featureSet contains the flag values that can be listed in assumes entries 42 // that this ubuntu-core actually provides. 43 var featureSet = map[string]bool{ 44 // Support for common data directory across revisions of a snap. 45 "common-data-dir": true, 46 // Support for the "Environment:" feature in snap.yaml 47 "snap-env": true, 48 // Support for the "command-chain" feature for apps and hooks in snap.yaml 49 "command-chain": true, 50 // Support for "kernel-assets" in gadget.yaml. I.e. having volume 51 // content of the style $kernel:ref` 52 "kernel-assets": true, 53 } 54 55 func checkAssumes(si *snap.Info) error { 56 missing := ([]string)(nil) 57 for _, flag := range si.Assumes { 58 if strings.HasPrefix(flag, "snapd") && checkVersion(flag[5:]) { 59 continue 60 } 61 if !featureSet[flag] { 62 missing = append(missing, flag) 63 } 64 } 65 if len(missing) > 0 { 66 return fmt.Errorf("snap %q assumes unsupported features: %s (try to refresh snapd)", si.InstanceName(), strings.Join(missing, ", ")) 67 } 68 return nil 69 } 70 71 // regular expression which matches a version expressed as groups of digits 72 // separated with dots, with optional non-numbers afterwards 73 var versionExp = regexp.MustCompile(`^(?:[1-9][0-9]*)(?:\.(?:[0-9]+))*`) 74 75 func checkVersion(version string) bool { 76 // double check that the input looks like a snapd version 77 reqVersionNumMatch := versionExp.FindStringSubmatch(version) 78 if reqVersionNumMatch == nil { 79 return false 80 } 81 // this check ensures that no one can use an assumes like snapd2.48.3~pre2 82 // or snapd2.48.5+20.10, as modifiers past the version number are not meant 83 // to be relied on for snaps via assumes, however the check against the real 84 // snapd version number below allows such non-numeric modifiers since real 85 // snapds do have versions like that (for example debian pkg of snapd) 86 if reqVersionNumMatch[0] != version { 87 return false 88 } 89 90 req := strings.Split(reqVersionNumMatch[0], ".") 91 92 if snapdtool.Version == "unknown" { 93 return true // Development tree. 94 } 95 96 // We could (should?) use strutil.VersionCompare here and simplify 97 // this code (see PR#7344). However this would change current 98 // behavior, i.e. "2.41~pre1" would *not* match [snapd2.41] anymore 99 // (which the code below does). 100 curVersionNumMatch := versionExp.FindStringSubmatch(snapdtool.Version) 101 if curVersionNumMatch == nil { 102 return false 103 } 104 cur := strings.Split(curVersionNumMatch[0], ".") 105 106 for i := range req { 107 if i == len(cur) { 108 // we hit the end of the elements of the current version number and have 109 // more required version numbers left, so this doesn't match, if the 110 // previous element was higher we would have broken out already, so the 111 // only case left here is where we have version requirements that are 112 // not met 113 return false 114 } 115 reqN, err1 := strconv.Atoi(req[i]) 116 curN, err2 := strconv.Atoi(cur[i]) 117 if err1 != nil || err2 != nil { 118 panic("internal error: version regexp is broken") 119 } 120 if curN != reqN { 121 return curN > reqN 122 } 123 } 124 125 return true 126 } 127 128 type SnapNeedsDevModeError struct { 129 Snap string 130 } 131 132 func (e *SnapNeedsDevModeError) Error() string { 133 return fmt.Sprintf("snap %q requires devmode or confinement override", e.Snap) 134 } 135 136 type SnapNeedsClassicError struct { 137 Snap string 138 } 139 140 func (e *SnapNeedsClassicError) Error() string { 141 return fmt.Sprintf("snap %q requires classic confinement", e.Snap) 142 } 143 144 type SnapNeedsClassicSystemError struct { 145 Snap string 146 } 147 148 func (e *SnapNeedsClassicSystemError) Error() string { 149 return fmt.Sprintf("snap %q requires classic confinement which is only available on classic systems", e.Snap) 150 } 151 152 type SnapNotClassicError struct { 153 Snap string 154 } 155 156 func (e *SnapNotClassicError) Error() string { 157 return fmt.Sprintf("snap %q is not a classic confined snap", e.Snap) 158 } 159 160 // determine whether the flags (and system overrides thereof) are 161 // compatible with the given *snap.Info 162 func validateFlagsForInfo(info *snap.Info, snapst *SnapState, flags Flags) error { 163 if flags.Classic && !info.NeedsClassic() { 164 return &SnapNotClassicError{Snap: info.InstanceName()} 165 } 166 167 switch c := info.Confinement; c { 168 case snap.StrictConfinement, "": 169 // strict is always fine 170 return nil 171 case snap.DevModeConfinement: 172 // --devmode needs to be specified every time (==> ignore snapst) 173 if flags.DevModeAllowed() { 174 return nil 175 } 176 return &SnapNeedsDevModeError{ 177 Snap: info.InstanceName(), 178 } 179 case snap.ClassicConfinement: 180 if !release.OnClassic { 181 return &SnapNeedsClassicSystemError{Snap: info.InstanceName()} 182 } 183 184 if flags.Classic { 185 return nil 186 } 187 188 if snapst != nil && snapst.Flags.Classic { 189 return nil 190 } 191 192 return &SnapNeedsClassicError{ 193 Snap: info.InstanceName(), 194 } 195 default: 196 return fmt.Errorf("unknown confinement %q", c) 197 } 198 } 199 200 // do a reasonably lightweight check that a snap described by Info, 201 // with the given SnapState and the user-specified Flags should be 202 // installable on the current system. 203 func validateInfoAndFlags(info *snap.Info, snapst *SnapState, flags Flags) error { 204 if err := validateFlagsForInfo(info, snapst, flags); err != nil { 205 return err 206 } 207 208 // verify we have a valid architecture 209 if !arch.IsSupportedArchitecture(info.Architectures) { 210 return fmt.Errorf("snap %q supported architectures (%s) are incompatible with this system (%s)", info.InstanceName(), strings.Join(info.Architectures, ", "), arch.DpkgArchitecture()) 211 } 212 213 // check assumes 214 if err := checkAssumes(info); err != nil { 215 return err 216 } 217 218 // check and create system-usernames 219 if err := checkAndCreateSystemUsernames(info); err != nil { 220 return err 221 } 222 223 return nil 224 } 225 226 var openSnapFile = backend.OpenSnapFile 227 228 func validateContainer(c snap.Container, s *snap.Info, logf func(format string, v ...interface{})) error { 229 err := snap.ValidateContainer(c, s, logf) 230 if err == nil { 231 return nil 232 } 233 return fmt.Errorf("%v; contact developer", err) 234 } 235 236 // checkSnap ensures that the snap can be installed. 237 func checkSnap(st *state.State, snapFilePath, instanceName string, si *snap.SideInfo, curInfo *snap.Info, flags Flags, deviceCtx DeviceContext) error { 238 // This assumes that the snap was already verified or --dangerous was used. 239 240 s, c, err := openSnapFile(snapFilePath, si) 241 if err != nil { 242 return err 243 } 244 245 if err := validateInfoAndFlags(s, nil, flags); err != nil { 246 return err 247 } 248 249 if err := validateContainer(c, s, logger.Noticef); err != nil { 250 return err 251 } 252 253 snapName, instanceKey := snap.SplitInstanceName(instanceName) 254 // update instance key to what was requested 255 s.InstanceKey = instanceKey 256 257 st.Lock() 258 defer st.Unlock() 259 260 // allow registered checks to run first as they may produce more 261 // precise errors 262 for _, check := range checkSnapCallbacks { 263 err := check(st, s, curInfo, c, flags, deviceCtx) 264 if err != nil { 265 return err 266 } 267 } 268 269 if snapName != s.SnapName() { 270 return fmt.Errorf("cannot install snap %q using instance name %q", s.SnapName(), instanceName) 271 } 272 273 return nil 274 } 275 276 // CheckSnapCallback defines callbacks for checking a snap for installation or refresh. 277 type CheckSnapCallback func(st *state.State, snap, curSnap *snap.Info, snapf snap.Container, flags Flags, deviceCtx DeviceContext) error 278 279 var checkSnapCallbacks []CheckSnapCallback 280 281 // AddCheckSnapCallback installs a callback to check a snap for installation or refresh. 282 func AddCheckSnapCallback(check CheckSnapCallback) { 283 checkSnapCallbacks = append(checkSnapCallbacks, check) 284 } 285 286 func MockCheckSnapCallbacks(checks []CheckSnapCallback) (restore func()) { 287 prev := checkSnapCallbacks 288 checkSnapCallbacks = checks 289 return func() { 290 checkSnapCallbacks = prev 291 } 292 } 293 294 func checkSnapdName(st *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, flags Flags, deviceCtx DeviceContext) error { 295 if snapInfo.Type() != snap.TypeSnapd { 296 // not a relevant check 297 return nil 298 } 299 if snapInfo.InstanceName() != "snapd" { 300 return fmt.Errorf(`cannot install snap %q of type "snapd" with a name other than "snapd"`, snapInfo.InstanceName()) 301 } 302 303 return nil 304 } 305 306 func checkCoreName(st *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, flags Flags, deviceCtx DeviceContext) error { 307 if snapInfo.Type() != snap.TypeOS { 308 // not a relevant check 309 return nil 310 } 311 if curInfo != nil { 312 // already one of these installed 313 return nil 314 } 315 core, err := coreInfo(st) 316 if err == state.ErrNoState { 317 return nil 318 } 319 if err != nil { 320 return err 321 } 322 323 // Allow installing "core" even if "ubuntu-core" is already 324 // installed. Ideally we should only allow this if we know 325 // this install is part of the ubuntu-core->core transition 326 // (e.g. via a flag) because if this happens outside of this 327 // transition we will end up with not connected interface 328 // connections in the "core" snap. But the transition will 329 // kick in automatically quickly so an extra flag is overkill. 330 if snapInfo.InstanceName() == "core" && core.InstanceName() == "ubuntu-core" { 331 return nil 332 } 333 334 // but generally do not allow to have two cores installed 335 if core.InstanceName() != snapInfo.InstanceName() { 336 return fmt.Errorf("cannot install core snap %q when core snap %q is already present", snapInfo.InstanceName(), core.InstanceName()) 337 } 338 339 return nil 340 } 341 342 func checkGadgetOrKernel(st *state.State, snapInfo, curInfo *snap.Info, snapf snap.Container, flags Flags, deviceCtx DeviceContext) error { 343 typ := snapInfo.Type() 344 kind := "" 345 var whichName func(*asserts.Model) string 346 switch typ { 347 case snap.TypeGadget: 348 kind = "gadget" 349 whichName = (*asserts.Model).Gadget 350 case snap.TypeKernel: 351 kind = "kernel" 352 whichName = (*asserts.Model).Kernel 353 default: 354 // not a relevant check 355 return nil 356 } 357 358 ok, err := HasSnapOfType(st, typ) 359 if err != nil { 360 return fmt.Errorf("cannot detect original %s snap: %v", kind, err) 361 } 362 // in firstboot we have no gadget/kernel yet - that is ok 363 // first install rules are in devicestate! 364 if !ok { 365 return nil 366 } 367 368 currentSnap, err := infoForDeviceSnap(st, deviceCtx, kind, whichName) 369 if err == state.ErrNoState { 370 // check if we are in the remodel case 371 if deviceCtx != nil && deviceCtx.ForRemodeling() { 372 if whichName(deviceCtx.Model()) == snapInfo.InstanceName() { 373 return nil 374 } 375 } 376 return fmt.Errorf("internal error: no state for %s snap %q", kind, snapInfo.InstanceName()) 377 } 378 if err != nil { 379 return fmt.Errorf("cannot find original %s snap: %v", kind, err) 380 } 381 382 if currentSnap.SnapID != "" && snapInfo.SnapID == "" { 383 return fmt.Errorf("cannot replace signed %s snap with an unasserted one", kind) 384 } 385 386 if currentSnap.SnapID != "" && snapInfo.SnapID != "" { 387 if currentSnap.SnapID == snapInfo.SnapID { 388 // same snap 389 return nil 390 } 391 return fmt.Errorf("cannot replace %s snap with a different one", kind) 392 } 393 394 if currentSnap.InstanceName() != snapInfo.InstanceName() { 395 return fmt.Errorf("cannot replace %s snap with a different one", kind) 396 } 397 398 return nil 399 } 400 401 func checkBases(st *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, flags Flags, deviceCtx DeviceContext) error { 402 // check if this is relevant 403 if snapInfo.Type() != snap.TypeApp && snapInfo.Type() != snap.TypeGadget { 404 return nil 405 } 406 if snapInfo.Base == "" { 407 return nil 408 } 409 if snapInfo.Base == "none" { 410 return nil 411 } 412 413 snapStates, err := All(st) 414 if err != nil { 415 return err 416 } 417 for otherSnap, snapst := range snapStates { 418 typ, err := snapst.Type() 419 if err != nil { 420 return err 421 } 422 if typ == snap.TypeBase && otherSnap == snapInfo.Base { 423 return nil 424 } 425 // core can be used instead for core16 426 if snapInfo.Base == "core16" && otherSnap == "core" { 427 return nil 428 } 429 } 430 431 return fmt.Errorf("cannot find required base %q", snapInfo.Base) 432 } 433 434 func checkEpochs(_ *state.State, snapInfo, curInfo *snap.Info, _ snap.Container, _ Flags, deviceCtx DeviceContext) error { 435 if curInfo == nil { 436 return nil 437 } 438 if snapInfo.Epoch.CanRead(curInfo.Epoch) { 439 return nil 440 } 441 desc := "local snap" 442 if snapInfo.SideInfo.Revision.Store() { 443 desc = fmt.Sprintf("new revision %s", snapInfo.SideInfo.Revision) 444 } 445 446 return fmt.Errorf("cannot refresh %q to %s with epoch %s, because it can't read the current epoch of %s", snapInfo.InstanceName(), desc, snapInfo.Epoch, curInfo.Epoch) 447 } 448 449 // check that the snap installed in the system (via snapst) can be 450 // upgraded to info (i.e. that info's epoch can read sanpst's epoch) 451 func earlyEpochCheck(info *snap.Info, snapst *SnapState) error { 452 if snapst == nil { 453 // no snapst, no problem 454 return nil 455 } 456 cur, err := snapst.CurrentInfo() 457 if err != nil { 458 if err == ErrNoCurrent { 459 // refreshing a disabled snap (maybe via InstallPath) 460 return nil 461 } 462 return err 463 } 464 465 return checkEpochs(nil, info, cur, nil, Flags{}, nil) 466 } 467 468 func earlyChecks(st *state.State, snapst *SnapState, update *snap.Info, flags Flags) (Flags, error) { 469 flags, err := ensureInstallPreconditions(st, update, flags, snapst) 470 if err != nil { 471 return flags, err 472 } 473 474 if err := earlyEpochCheck(update, snapst); err != nil { 475 return flags, err 476 } 477 return flags, nil 478 } 479 480 // check that the listed system users are valid 481 var osutilEnsureUserGroup = osutil.EnsureUserGroup 482 483 func validateSystemUsernames(si *snap.Info) error { 484 for _, user := range si.SystemUsernames { 485 systemUserName, ok := snap.SupportedSystemUsernames[user.Name] 486 if !ok { 487 return fmt.Errorf(`snap %q requires unsupported system username "%s"`, si.InstanceName(), user.Name) 488 } 489 490 if systemUserName.AllowedSnapIds != nil && si.SnapID != "" { 491 // Only certain snaps can use this user; let's check whether ours 492 // is one of these 493 if !strutil.ListContains(systemUserName.AllowedSnapIds, si.SnapID) { 494 return fmt.Errorf(`snap %q is not allowed to use the system user %q`, 495 si.InstanceName(), user.Name) 496 } 497 } 498 499 switch user.Scope { 500 case "shared": 501 // this is supported 502 continue 503 case "private", "external": 504 // not supported yet 505 return fmt.Errorf(`snap %q requires unsupported user scope "%s" for this version of snapd`, si.InstanceName(), user.Scope) 506 default: 507 return fmt.Errorf(`snap %q requires unsupported user scope "%s"`, si.InstanceName(), user.Scope) 508 } 509 } 510 return nil 511 } 512 513 func checkAndCreateSystemUsernames(si *snap.Info) error { 514 // No need to check support if no system-usernames 515 if len(si.SystemUsernames) == 0 { 516 return nil 517 } 518 519 // Run /.../snap-seccomp version-info 520 vi, err := seccomp_compiler.CompilerVersionInfo(snapdtool.InternalToolPath) 521 if err != nil { 522 return fmt.Errorf("cannot obtain seccomp compiler information: %v", err) 523 } 524 525 // If the system doesn't support robust argument filtering then we 526 // can't support system-usernames 527 if err := vi.SupportsRobustArgumentFiltering(); err != nil { 528 if re, ok := err.(*seccomp_compiler.BuildTimeRequirementError); ok { 529 return fmt.Errorf("snap %q system usernames require a snapd built against %s", si.InstanceName(), re.RequirementsString()) 530 } 531 return err 532 } 533 534 // first validate 535 if err := validateSystemUsernames(si); err != nil { 536 return err 537 } 538 539 // then create 540 // TODO: move user creation to a more appropriate place like "link-snap" 541 extrausers := !release.OnClassic 542 for _, user := range si.SystemUsernames { 543 id := snap.SupportedSystemUsernames[user.Name].Id 544 switch user.Scope { 545 case "shared": 546 // Create the snapd-range-<base>-root user and group so 547 // systemd-nspawn can avoid our range. Our ranges will always 548 // be in 65536 chunks, so mask off the lower bits to obtain our 549 // base (see above) 550 rangeStart := id & 0xFFFF0000 551 rangeName := fmt.Sprintf("snapd-range-%d-root", rangeStart) 552 if err := osutilEnsureUserGroup(rangeName, rangeStart, extrausers); err != nil { 553 return fmt.Errorf(`cannot ensure users for snap %q required system username "%s": %v`, si.InstanceName(), user.Name, err) 554 } 555 556 // Create the requested user and group 557 if err := osutilEnsureUserGroup(user.Name, id, extrausers); err != nil { 558 return fmt.Errorf(`cannot ensure users for snap %q required system username "%s": %v`, si.InstanceName(), user.Name, err) 559 } 560 } 561 } 562 return nil 563 } 564 565 func init() { 566 AddCheckSnapCallback(checkCoreName) 567 AddCheckSnapCallback(checkSnapdName) 568 AddCheckSnapCallback(checkGadgetOrKernel) 569 AddCheckSnapCallback(checkBases) 570 AddCheckSnapCallback(checkEpochs) 571 }