github.com/rigado/snapd@v2.42.5-go-mod+incompatible/snap/snapenv/snapenv.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2015 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 snapenv
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"os/user"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	"github.com/snapcore/snapd/arch"
    30  	"github.com/snapcore/snapd/dirs"
    31  	"github.com/snapcore/snapd/osutil/sys"
    32  	"github.com/snapcore/snapd/snap"
    33  )
    34  
    35  type preserveUnsafeEnvFlag int8
    36  
    37  const (
    38  	discardUnsafeFlag preserveUnsafeEnvFlag = iota
    39  	preserveUnsafeFlag
    40  )
    41  
    42  // ExecEnv returns the full environment that is required for
    43  // snap-{confine,exec}(like SNAP_{NAME,REVISION} etc are all set).
    44  //
    45  // It merges it with the existing os.Environ() and ensures the SNAP_*
    46  // overrides the any pre-existing environment variables. For a classic
    47  // snap, environment variables that are usually stripped out by ld.so
    48  // when starting a setuid process are renamed by prepending
    49  // PreservedUnsafePrefix -- which snap-exec will remove, restoring the
    50  // variables to their original names.
    51  //
    52  // With the extra parameter additional environment variables can be
    53  // supplied which will be set in the execution environment.
    54  func ExecEnv(info *snap.Info, extra map[string]string) []string {
    55  	// merge environment and the snap environment, note that the
    56  	// snap environment overrides pre-existing env entries
    57  	preserve := discardUnsafeFlag
    58  	if info.NeedsClassic() {
    59  		preserve = preserveUnsafeFlag
    60  	}
    61  	env := envMap(os.Environ(), preserve)
    62  	snapEnv := snapEnv(info)
    63  	for k, v := range snapEnv {
    64  		env[k] = v
    65  	}
    66  	for k, v := range extra {
    67  		env[k] = v
    68  	}
    69  	return envFromMap(env)
    70  }
    71  
    72  // snapEnv returns the extra environment that is required for
    73  // snap-{confine,exec} to work.
    74  func snapEnv(info *snap.Info) map[string]string {
    75  	var home string
    76  
    77  	usr, err := user.Current()
    78  	if err == nil {
    79  		home = usr.HomeDir
    80  	}
    81  
    82  	env := basicEnv(info)
    83  	if home != "" {
    84  		for k, v := range userEnv(info, home) {
    85  			env[k] = v
    86  		}
    87  	}
    88  	return env
    89  }
    90  
    91  // basicEnv returns the app-level environment variables for a snap.
    92  // Despite this being a bit snap-specific, this is in helpers.go because it's
    93  // used by so many other modules, we run into circular dependencies if it's
    94  // somewhere more reasonable like the snappy module.
    95  func basicEnv(info *snap.Info) map[string]string {
    96  	return map[string]string{
    97  		// This uses CoreSnapMountDir because the computed environment
    98  		// variables are conveyed to the started application process which
    99  		// shall *either* execute with the new mount namespace where snaps are
   100  		// always mounted on /snap OR it is a classically confined snap where
   101  		// /snap is a part of the distribution package.
   102  		//
   103  		// For parallel-installs the mount namespace setup is making the
   104  		// environment of each snap instance appear as if it's the only
   105  		// snap, i.e. SNAP paths point to the same locations within the
   106  		// mount namespace
   107  		"SNAP":               filepath.Join(dirs.CoreSnapMountDir, info.SnapName(), info.Revision.String()),
   108  		"SNAP_COMMON":        snap.CommonDataDir(info.SnapName()),
   109  		"SNAP_DATA":          snap.DataDir(info.SnapName(), info.Revision),
   110  		"SNAP_NAME":          info.SnapName(),
   111  		"SNAP_INSTANCE_NAME": info.InstanceName(),
   112  		"SNAP_INSTANCE_KEY":  info.InstanceKey,
   113  		"SNAP_VERSION":       info.Version,
   114  		"SNAP_REVISION":      info.Revision.String(),
   115  		"SNAP_ARCH":          arch.DpkgArchitecture(),
   116  		// see https://github.com/snapcore/snapd/pull/2732#pullrequestreview-18827193
   117  		"SNAP_LIBRARY_PATH": "/var/lib/snapd/lib/gl:/var/lib/snapd/lib/gl32:/var/lib/snapd/void",
   118  		"SNAP_REEXEC":       os.Getenv("SNAP_REEXEC"),
   119  	}
   120  }
   121  
   122  // userEnv returns the user-level environment variables for a snap.
   123  // Despite this being a bit snap-specific, this is in helpers.go because it's
   124  // used by so many other modules, we run into circular dependencies if it's
   125  // somewhere more reasonable like the snappy module.
   126  func userEnv(info *snap.Info, home string) map[string]string {
   127  	// To keep things simple the user variables always point to the
   128  	// instance-specific directories.
   129  	result := map[string]string{
   130  		"SNAP_USER_COMMON": info.UserCommonDataDir(home),
   131  		"SNAP_USER_DATA":   info.UserDataDir(home),
   132  		"XDG_RUNTIME_DIR":  info.UserXdgRuntimeDir(sys.Geteuid()),
   133  	}
   134  	// For non-classic snaps, we set HOME but on classic allow snaps to see real HOME
   135  	if !info.NeedsClassic() {
   136  		result["HOME"] = info.UserDataDir(home)
   137  	}
   138  	return result
   139  }
   140  
   141  // Environment variables glibc strips out when running a setuid binary.
   142  // Taken from https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/generic/unsecvars.h;hb=HEAD
   143  // TODO: use go generate to obtain this list at build time.
   144  var unsafeEnv = map[string]bool{
   145  	"GCONV_PATH":       true,
   146  	"GETCONF_DIR":      true,
   147  	"GLIBC_TUNABLES":   true,
   148  	"HOSTALIASES":      true,
   149  	"LD_AUDIT":         true,
   150  	"LD_DEBUG":         true,
   151  	"LD_DEBUG_OUTPUT":  true,
   152  	"LD_DYNAMIC_WEAK":  true,
   153  	"LD_HWCAP_MASK":    true,
   154  	"LD_LIBRARY_PATH":  true,
   155  	"LD_ORIGIN_PATH":   true,
   156  	"LD_PRELOAD":       true,
   157  	"LD_PROFILE":       true,
   158  	"LD_SHOW_AUXV":     true,
   159  	"LD_USE_LOAD_BIAS": true,
   160  	"LOCALDOMAIN":      true,
   161  	"LOCPATH":          true,
   162  	"MALLOC_TRACE":     true,
   163  	"NIS_PATH":         true,
   164  	"NLSPATH":          true,
   165  	"RESOLV_HOST_CONF": true,
   166  	"RES_OPTIONS":      true,
   167  	"TMPDIR":           true,
   168  	"TZDIR":            true,
   169  }
   170  
   171  const PreservedUnsafePrefix = "SNAP_SAVED_"
   172  
   173  // envMap creates a map from the given environment string list,
   174  // e.g. the list returned from os.Environ(). If preserveUnsafeVars
   175  // rename variables that will be stripped out by the dynamic linker
   176  // executing the setuid snap-confine by prepending their names with
   177  // PreservedUnsafePrefix.
   178  func envMap(env []string, preserveUnsafeEnv preserveUnsafeEnvFlag) map[string]string {
   179  	envMap := map[string]string{}
   180  	for _, kv := range env {
   181  		// snap-exec unconditionally renames variables
   182  		// starting with PreservedUnsafePrefix so skip any
   183  		// that are already present in the environment to
   184  		// avoid confusion.
   185  		if strings.HasPrefix(kv, PreservedUnsafePrefix) {
   186  			continue
   187  		}
   188  		l := strings.SplitN(kv, "=", 2)
   189  		if len(l) < 2 {
   190  			continue // strange
   191  		}
   192  		k, v := l[0], l[1]
   193  		if preserveUnsafeEnv == preserveUnsafeFlag && unsafeEnv[k] {
   194  			k = PreservedUnsafePrefix + k
   195  		}
   196  		envMap[k] = v
   197  	}
   198  	return envMap
   199  }
   200  
   201  // envFromMap creates a list of strings of the form k=v from a dict. This is
   202  // useful in combination with envMap to create an environment suitable to
   203  // pass to e.g. syscall.Exec()
   204  func envFromMap(em map[string]string) []string {
   205  	var out []string
   206  	for k, v := range em {
   207  		out = append(out, fmt.Sprintf("%s=%s", k, v))
   208  	}
   209  	return out
   210  }