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