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 }