github.com/moby/docker@v26.1.3+incompatible/daemon/container_operations_unix.go (about) 1 //go:build linux || freebsd 2 3 package daemon // import "github.com/docker/docker/daemon" 4 5 import ( 6 "context" 7 "fmt" 8 "os" 9 "path/filepath" 10 "strconv" 11 "syscall" 12 13 "github.com/containerd/log" 14 "github.com/docker/docker/container" 15 "github.com/docker/docker/daemon/config" 16 "github.com/docker/docker/daemon/links" 17 "github.com/docker/docker/errdefs" 18 "github.com/docker/docker/libnetwork" 19 "github.com/docker/docker/pkg/idtools" 20 "github.com/docker/docker/pkg/process" 21 "github.com/docker/docker/pkg/stringid" 22 "github.com/docker/docker/runconfig" 23 "github.com/moby/sys/mount" 24 "github.com/opencontainers/selinux/go-selinux/label" 25 "github.com/pkg/errors" 26 "golang.org/x/sys/unix" 27 ) 28 29 func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) { 30 var env []string 31 children := daemon.children(container) 32 33 bridgeSettings := container.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()] 34 if bridgeSettings == nil || bridgeSettings.EndpointSettings == nil { 35 return nil, nil 36 } 37 38 for linkAlias, child := range children { 39 if !child.IsRunning() { 40 return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, linkAlias) 41 } 42 43 childBridgeSettings := child.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()] 44 if childBridgeSettings == nil || childBridgeSettings.EndpointSettings == nil { 45 return nil, fmt.Errorf("container %s not attached to default bridge network", child.ID) 46 } 47 48 link := links.NewLink( 49 bridgeSettings.IPAddress, 50 childBridgeSettings.IPAddress, 51 linkAlias, 52 child.Config.Env, 53 child.Config.ExposedPorts, 54 ) 55 56 env = append(env, link.ToEnv()...) 57 } 58 59 return env, nil 60 } 61 62 func (daemon *Daemon) getIPCContainer(id string) (*container.Container, error) { 63 // Check if the container exists, is running, and not restarting 64 ctr, err := daemon.GetContainer(id) 65 if err != nil { 66 return nil, errdefs.InvalidParameter(err) 67 } 68 if !ctr.IsRunning() { 69 return nil, errNotRunning(id) 70 } 71 if ctr.IsRestarting() { 72 return nil, errContainerIsRestarting(id) 73 } 74 75 // Check the container ipc is shareable 76 if st, err := os.Stat(ctr.ShmPath); err != nil || !st.IsDir() { 77 if err == nil || os.IsNotExist(err) { 78 return nil, errdefs.InvalidParameter(errors.New("container " + id + ": non-shareable IPC (hint: use IpcMode:shareable for the donor container)")) 79 } 80 // stat() failed? 81 return nil, errdefs.System(errors.Wrap(err, "container "+id)) 82 } 83 84 return ctr, nil 85 } 86 87 func (daemon *Daemon) getPIDContainer(id string) (*container.Container, error) { 88 ctr, err := daemon.GetContainer(id) 89 if err != nil { 90 return nil, errdefs.InvalidParameter(err) 91 } 92 if !ctr.IsRunning() { 93 return nil, errNotRunning(id) 94 } 95 if ctr.IsRestarting() { 96 return nil, errContainerIsRestarting(id) 97 } 98 99 return ctr, nil 100 } 101 102 // setupContainerDirs sets up base container directories (root, ipc, tmpfs and secrets). 103 func (daemon *Daemon) setupContainerDirs(c *container.Container) (_ []container.Mount, err error) { 104 if err := daemon.setupContainerMountsRoot(c); err != nil { 105 return nil, err 106 } 107 108 if err := daemon.setupIPCDirs(c); err != nil { 109 return nil, err 110 } 111 112 if err := daemon.setupSecretDir(c); err != nil { 113 return nil, err 114 } 115 defer func() { 116 if err != nil { 117 daemon.cleanupSecretDir(c) 118 } 119 }() 120 121 var ms []container.Mount 122 if !c.HostConfig.IpcMode.IsPrivate() && !c.HostConfig.IpcMode.IsEmpty() { 123 ms = append(ms, c.IpcMounts()...) 124 } 125 126 tmpfsMounts, err := c.TmpfsMounts() 127 if err != nil { 128 return nil, err 129 } 130 ms = append(ms, tmpfsMounts...) 131 132 secretMounts, err := c.SecretMounts() 133 if err != nil { 134 return nil, err 135 } 136 ms = append(ms, secretMounts...) 137 138 return ms, nil 139 } 140 141 func (daemon *Daemon) setupIPCDirs(c *container.Container) error { 142 ipcMode := c.HostConfig.IpcMode 143 144 switch { 145 case ipcMode.IsContainer(): 146 ic, err := daemon.getIPCContainer(ipcMode.Container()) 147 if err != nil { 148 return errors.Wrapf(err, "failed to join IPC namespace") 149 } 150 c.ShmPath = ic.ShmPath 151 152 case ipcMode.IsHost(): 153 if _, err := os.Stat("/dev/shm"); err != nil { 154 return fmt.Errorf("/dev/shm is not mounted, but must be for --ipc=host") 155 } 156 c.ShmPath = "/dev/shm" 157 158 case ipcMode.IsPrivate(), ipcMode.IsNone(): 159 // c.ShmPath will/should not be used, so make it empty. 160 // Container's /dev/shm mount comes from OCI spec. 161 c.ShmPath = "" 162 163 case ipcMode.IsEmpty(): 164 // A container was created by an older version of the daemon. 165 // The default behavior used to be what is now called "shareable". 166 fallthrough 167 168 case ipcMode.IsShareable(): 169 rootIDs := daemon.idMapping.RootPair() 170 if !c.HasMountFor("/dev/shm") { 171 shmPath, err := c.ShmResourcePath() 172 if err != nil { 173 return err 174 } 175 176 if err := idtools.MkdirAllAndChown(shmPath, 0o700, rootIDs); err != nil { 177 return err 178 } 179 180 shmproperty := "mode=1777,size=" + strconv.FormatInt(c.HostConfig.ShmSize, 10) 181 if err := unix.Mount("shm", shmPath, "tmpfs", uintptr(unix.MS_NOEXEC|unix.MS_NOSUID|unix.MS_NODEV), label.FormatMountLabel(shmproperty, c.GetMountLabel())); err != nil { 182 return fmt.Errorf("mounting shm tmpfs: %s", err) 183 } 184 if err := os.Chown(shmPath, rootIDs.UID, rootIDs.GID); err != nil { 185 return err 186 } 187 c.ShmPath = shmPath 188 } 189 190 default: 191 return fmt.Errorf("invalid IPC mode: %v", ipcMode) 192 } 193 194 return nil 195 } 196 197 func (daemon *Daemon) setupSecretDir(c *container.Container) (setupErr error) { 198 if len(c.SecretReferences) == 0 && len(c.ConfigReferences) == 0 { 199 return nil 200 } 201 202 if err := daemon.createSecretsDir(c); err != nil { 203 return err 204 } 205 defer func() { 206 if setupErr != nil { 207 daemon.cleanupSecretDir(c) 208 } 209 }() 210 211 if c.DependencyStore == nil { 212 return fmt.Errorf("secret store is not initialized") 213 } 214 215 // retrieve possible remapped range start for root UID, GID 216 rootIDs := daemon.idMapping.RootPair() 217 218 for _, s := range c.SecretReferences { 219 // TODO (ehazlett): use type switch when more are supported 220 if s.File == nil { 221 log.G(context.TODO()).Error("secret target type is not a file target") 222 continue 223 } 224 225 // secrets are created in the SecretMountPath on the host, at a 226 // single level 227 fPath, err := c.SecretFilePath(*s) 228 if err != nil { 229 return errors.Wrap(err, "error getting secret file path") 230 } 231 if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0o700, rootIDs); err != nil { 232 return errors.Wrap(err, "error creating secret mount path") 233 } 234 235 log.G(context.TODO()).WithFields(log.Fields{ 236 "name": s.File.Name, 237 "path": fPath, 238 }).Debug("injecting secret") 239 secret, err := c.DependencyStore.Secrets().Get(s.SecretID) 240 if err != nil { 241 return errors.Wrap(err, "unable to get secret from secret store") 242 } 243 if err := os.WriteFile(fPath, secret.Spec.Data, s.File.Mode); err != nil { 244 return errors.Wrap(err, "error injecting secret") 245 } 246 247 uid, err := strconv.Atoi(s.File.UID) 248 if err != nil { 249 return err 250 } 251 gid, err := strconv.Atoi(s.File.GID) 252 if err != nil { 253 return err 254 } 255 256 if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil { 257 return errors.Wrap(err, "error setting ownership for secret") 258 } 259 if err := os.Chmod(fPath, s.File.Mode); err != nil { 260 return errors.Wrap(err, "error setting file mode for secret") 261 } 262 } 263 264 for _, configRef := range c.ConfigReferences { 265 // TODO (ehazlett): use type switch when more are supported 266 if configRef.File == nil { 267 // Runtime configs are not mounted into the container, but they're 268 // a valid type of config so we should not error when we encounter 269 // one. 270 if configRef.Runtime == nil { 271 log.G(context.TODO()).Error("config target type is not a file or runtime target") 272 } 273 // However, in any case, this isn't a file config, so we have no 274 // further work to do 275 continue 276 } 277 278 fPath, err := c.ConfigFilePath(*configRef) 279 if err != nil { 280 return errors.Wrap(err, "error getting config file path for container") 281 } 282 if err := idtools.MkdirAllAndChown(filepath.Dir(fPath), 0o700, rootIDs); err != nil { 283 return errors.Wrap(err, "error creating config mount path") 284 } 285 286 log.G(context.TODO()).WithFields(log.Fields{ 287 "name": configRef.File.Name, 288 "path": fPath, 289 }).Debug("injecting config") 290 config, err := c.DependencyStore.Configs().Get(configRef.ConfigID) 291 if err != nil { 292 return errors.Wrap(err, "unable to get config from config store") 293 } 294 if err := os.WriteFile(fPath, config.Spec.Data, configRef.File.Mode); err != nil { 295 return errors.Wrap(err, "error injecting config") 296 } 297 298 uid, err := strconv.Atoi(configRef.File.UID) 299 if err != nil { 300 return err 301 } 302 gid, err := strconv.Atoi(configRef.File.GID) 303 if err != nil { 304 return err 305 } 306 307 if err := os.Chown(fPath, rootIDs.UID+uid, rootIDs.GID+gid); err != nil { 308 return errors.Wrap(err, "error setting ownership for config") 309 } 310 if err := os.Chmod(fPath, configRef.File.Mode); err != nil { 311 return errors.Wrap(err, "error setting file mode for config") 312 } 313 } 314 315 return daemon.remountSecretDir(c) 316 } 317 318 // createSecretsDir is used to create a dir suitable for storing container secrets. 319 // In practice this is using a tmpfs mount and is used for both "configs" and "secrets" 320 func (daemon *Daemon) createSecretsDir(c *container.Container) error { 321 // retrieve possible remapped range start for root UID, GID 322 rootIDs := daemon.idMapping.RootPair() 323 dir, err := c.SecretMountPath() 324 if err != nil { 325 return errors.Wrap(err, "error getting container secrets dir") 326 } 327 328 // create tmpfs 329 if err := idtools.MkdirAllAndChown(dir, 0o700, rootIDs); err != nil { 330 return errors.Wrap(err, "error creating secret local mount path") 331 } 332 333 tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID) 334 if err := mount.Mount("tmpfs", dir, "tmpfs", "nodev,nosuid,noexec,"+tmpfsOwnership); err != nil { 335 return errors.Wrap(err, "unable to setup secret mount") 336 } 337 return nil 338 } 339 340 func (daemon *Daemon) remountSecretDir(c *container.Container) error { 341 dir, err := c.SecretMountPath() 342 if err != nil { 343 return errors.Wrap(err, "error getting container secrets path") 344 } 345 if err := label.Relabel(dir, c.MountLabel, false); err != nil { 346 log.G(context.TODO()).WithError(err).WithField("dir", dir).Warn("Error while attempting to set selinux label") 347 } 348 rootIDs := daemon.idMapping.RootPair() 349 tmpfsOwnership := fmt.Sprintf("uid=%d,gid=%d", rootIDs.UID, rootIDs.GID) 350 351 // remount secrets ro 352 if err := mount.Mount("tmpfs", dir, "tmpfs", "remount,ro,"+tmpfsOwnership); err != nil { 353 return errors.Wrap(err, "unable to remount dir as readonly") 354 } 355 356 return nil 357 } 358 359 func (daemon *Daemon) cleanupSecretDir(c *container.Container) { 360 dir, err := c.SecretMountPath() 361 if err != nil { 362 log.G(context.TODO()).WithError(err).WithField("container", c.ID).Warn("error getting secrets mount path for container") 363 } 364 if err := mount.RecursiveUnmount(dir); err != nil { 365 log.G(context.TODO()).WithField("dir", dir).WithError(err).Warn("Error while attempting to unmount dir, this may prevent removal of container.") 366 } 367 if err := os.RemoveAll(dir); err != nil { 368 log.G(context.TODO()).WithField("dir", dir).WithError(err).Error("Error removing dir.") 369 } 370 } 371 372 func killProcessDirectly(container *container.Container) error { 373 pid := container.GetPID() 374 if pid == 0 { 375 // Ensure that we don't kill ourselves 376 return nil 377 } 378 379 if err := unix.Kill(pid, syscall.SIGKILL); err != nil { 380 if err != unix.ESRCH { 381 return errdefs.System(err) 382 } 383 err = errNoSuchProcess{pid, syscall.SIGKILL} 384 log.G(context.TODO()).WithError(err).WithField("container", container.ID).Debug("no such process") 385 return err 386 } 387 388 // In case there were some exceptions(e.g., state of zombie and D) 389 if process.Alive(pid) { 390 // Since we can not kill a zombie pid, add zombie check here 391 isZombie, err := process.Zombie(pid) 392 if err != nil { 393 log.G(context.TODO()).WithError(err).WithField("container", container.ID).Warn("Container state is invalid") 394 return err 395 } 396 if isZombie { 397 return errdefs.System(errors.Errorf("container %s PID %d is zombie and can not be killed. Use the --init option when creating containers to run an init inside the container that forwards signals and reaps processes", stringid.TruncateID(container.ID), pid)) 398 } 399 } 400 return nil 401 } 402 403 func isLinkable(child *container.Container) bool { 404 // A container is linkable only if it belongs to the default network 405 _, ok := child.NetworkSettings.Networks[runconfig.DefaultDaemonNetworkMode().NetworkName()] 406 return ok 407 } 408 409 // TODO(aker): remove when we make the default bridge network behave like any other network 410 func enableIPOnPredefinedNetwork() bool { 411 return false 412 } 413 414 // serviceDiscoveryOnDefaultNetwork indicates if service discovery is supported on the default network 415 // TODO(aker): remove when we make the default bridge network behave like any other network 416 func serviceDiscoveryOnDefaultNetwork() bool { 417 return false 418 } 419 420 func buildSandboxPlatformOptions(container *container.Container, cfg *config.Config, sboxOptions *[]libnetwork.SandboxOption) error { 421 var err error 422 var originResolvConfPath string 423 424 // Set the correct paths for /etc/hosts and /etc/resolv.conf, based on the 425 // networking-mode of the container. Note that containers with "container" 426 // networking are already handled in "initializeNetworking()" before we reach 427 // this function, so do not have to be accounted for here. 428 switch { 429 case container.HostConfig.NetworkMode.IsHost(): 430 // In host-mode networking, the container does not have its own networking 431 // namespace, so both `/etc/hosts` and `/etc/resolv.conf` should be the same 432 // as on the host itself. The container gets a copy of these files. 433 *sboxOptions = append( 434 *sboxOptions, 435 libnetwork.OptionOriginHostsPath("/etc/hosts"), 436 ) 437 originResolvConfPath = "/etc/resolv.conf" 438 case container.HostConfig.NetworkMode.IsUserDefined(): 439 // The container uses a user-defined network. We use the embedded DNS 440 // server for container name resolution and to act as a DNS forwarder 441 // for external DNS resolution. 442 // We parse the DNS server(s) that are defined in /etc/resolv.conf on 443 // the host, which may be a local DNS server (for example, if DNSMasq or 444 // systemd-resolvd are in use). The embedded DNS server forwards DNS 445 // resolution to the DNS server configured on the host, which in itself 446 // may act as a forwarder for external DNS servers. 447 // If systemd-resolvd is used, the "upstream" DNS servers can be found in 448 // /run/systemd/resolve/resolv.conf. We do not query those DNS servers 449 // directly, as they can be dynamically reconfigured. 450 originResolvConfPath = "/etc/resolv.conf" 451 default: 452 // For other situations, such as the default bridge network, container 453 // discovery / name resolution is handled through /etc/hosts, and no 454 // embedded DNS server is available. Without the embedded DNS, we 455 // cannot use local DNS servers on the host (for example, if DNSMasq or 456 // systemd-resolvd is used). If systemd-resolvd is used, we try to 457 // determine the external DNS servers that are used on the host. 458 // This situation is not ideal, because DNS servers configured in the 459 // container are not updated after the container is created, but the 460 // DNS servers on the host can be dynamically updated. 461 // 462 // Copy the host's resolv.conf for the container (/run/systemd/resolve/resolv.conf or /etc/resolv.conf) 463 originResolvConfPath = cfg.GetResolvConf() 464 } 465 466 // Allow tests to point at their own resolv.conf file. 467 if envPath := os.Getenv("DOCKER_TEST_RESOLV_CONF_PATH"); envPath != "" { 468 log.G(context.TODO()).Infof("Using OriginResolvConfPath from env: %s", envPath) 469 originResolvConfPath = envPath 470 } 471 *sboxOptions = append(*sboxOptions, libnetwork.OptionOriginResolvConfPath(originResolvConfPath)) 472 473 container.HostsPath, err = container.GetRootResourcePath("hosts") 474 if err != nil { 475 return err 476 } 477 *sboxOptions = append(*sboxOptions, libnetwork.OptionHostsPath(container.HostsPath)) 478 479 container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") 480 if err != nil { 481 return err 482 } 483 *sboxOptions = append(*sboxOptions, libnetwork.OptionResolvConfPath(container.ResolvConfPath)) 484 485 return nil 486 } 487 488 func (daemon *Daemon) initializeNetworkingPaths(container *container.Container, nc *container.Container) error { 489 container.HostnamePath = nc.HostnamePath 490 container.HostsPath = nc.HostsPath 491 container.ResolvConfPath = nc.ResolvConfPath 492 return nil 493 } 494 495 func (daemon *Daemon) setupContainerMountsRoot(c *container.Container) error { 496 // get the root mount path so we can make it unbindable 497 p, err := c.MountsResourcePath("") 498 if err != nil { 499 return err 500 } 501 return idtools.MkdirAllAndChown(p, 0o710, idtools.Identity{UID: idtools.CurrentIdentity().UID, GID: daemon.IdentityMapping().RootPair().GID}) 502 }