github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/osutil/env.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 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 osutil
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"github.com/snapcore/snapd/strutil"
    30  )
    31  
    32  // GetenvBool returns whether the given key may be considered "set" in the
    33  // environment (i.e. it is set to one of "1", "true", etc).
    34  //
    35  // An optional second argument can be provided, which determines how to
    36  // treat missing or unparsable values; default is to treat them as false.
    37  func GetenvBool(key string, dflt ...bool) bool {
    38  	if val := strings.TrimSpace(os.Getenv(key)); val != "" {
    39  		if b, err := strconv.ParseBool(val); err == nil {
    40  			return b
    41  		}
    42  	}
    43  
    44  	if len(dflt) > 0 {
    45  		return dflt[0]
    46  	}
    47  
    48  	return false
    49  }
    50  
    51  // GetenvInt64 interprets the value of the given environment variable
    52  // as an int64 and returns the corresponding value. The base can be
    53  // implied via the prefix (0x for 16, 0 for 8; otherwise 10).
    54  //
    55  // An optional second argument can be provided, which determines how to
    56  // treat missing or unparsable values; default is to treat them as 0.
    57  func GetenvInt64(key string, dflt ...int64) int64 {
    58  	if val := strings.TrimSpace(os.Getenv(key)); val != "" {
    59  		if b, err := strconv.ParseInt(val, 0, 64); err == nil {
    60  			return b
    61  		}
    62  	}
    63  
    64  	if len(dflt) > 0 {
    65  		return dflt[0]
    66  	}
    67  
    68  	return 0
    69  }
    70  
    71  // Environment is an unordered map of key=value strings.
    72  //
    73  // Environment can be manipulated with available methods and eventually
    74  // converted to low-level representation necessary when executing programs.
    75  // This approach discourages operations that could result in duplicate
    76  // environment variable definitions from being constructed.
    77  type Environment map[string]string
    78  
    79  func parseEnvEntry(entry string) (string, string, error) {
    80  	parts := strings.SplitN(entry, "=", 2)
    81  	if len(parts) != 2 {
    82  		return "", "", fmt.Errorf("cannot parse environment entry: %q", entry)
    83  	}
    84  	key, value := parts[0], parts[1]
    85  	if key == "" {
    86  		return "", "", fmt.Errorf("environment variable name cannot be empty: %q", entry)
    87  	}
    88  	return key, value, nil
    89  }
    90  
    91  // parseRawEnvironment parsers raw environment.
    92  //
    93  // This function fails if any of the provided values are not in the form of
    94  // key=value or if there are duplicate keys.
    95  func parseRawEnvironment(raw []string) (Environment, error) {
    96  	env := make(Environment, len(raw))
    97  	for _, entry := range raw {
    98  		key, value, err := parseEnvEntry(entry)
    99  		if err != nil {
   100  			return nil, err
   101  		}
   102  		if _, ok := env[key]; ok {
   103  			return nil, fmt.Errorf("cannot overwrite earlier value of %q", key)
   104  		}
   105  		env[key] = value
   106  	}
   107  	return env, nil
   108  }
   109  
   110  // OSEnvironment returns the environment of the calling process.
   111  func OSEnvironment() (Environment, error) {
   112  	return parseRawEnvironment(os.Environ())
   113  }
   114  
   115  // OSEnvironmentUnescapeUnsafe returns the environment of the calling process.
   116  // It will also strip unsafeEscapePrefix from any variable starting with it.
   117  // Use-case/assumption is that ForExecEscapeUnsafe was used previously
   118  // along the exec chain.
   119  func OSEnvironmentUnescapeUnsafe(unsafeEscapePrefix string) (Environment, error) {
   120  	env, err := parseRawEnvironment(os.Environ())
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	for key, value := range env {
   126  		if newKey := strings.TrimPrefix(key, unsafeEscapePrefix); key != newKey {
   127  			delete(env, key)
   128  			if _, ok := env[newKey]; ok {
   129  				// assume newKey was originally
   130  				// dropped when the escaped key and
   131  				// value were set so current value is
   132  				// newer here, keep it
   133  				continue
   134  			}
   135  			env[newKey] = value
   136  		}
   137  	}
   138  	return env, nil
   139  }
   140  
   141  // ForExec returns the environment in a form suitable for using with
   142  // the exec family of functions.
   143  //
   144  // The returned environment is sorted lexicographically by variable name.
   145  func (env Environment) ForExec() []string {
   146  	raw := make([]string, 0, len(env))
   147  	keys := make([]string, 0, len(env))
   148  	for key := range env {
   149  		keys = append(keys, key)
   150  	}
   151  	sort.Strings(keys)
   152  	for _, key := range keys {
   153  		raw = append(raw, fmt.Sprintf("%s=%s", key, env[key]))
   154  	}
   155  	return raw
   156  }
   157  
   158  // ForExecEscapeUnsafe returns the environment in a form suitable for
   159  // using with the exec family of functions.
   160  //
   161  // Further variables that are usually stripped out by ld.so when starting a
   162  // setuid process are renamed by prepending unsafeEscapePrefix to
   163  // them.
   164  //
   165  // Unlikely variables already starting with the prefix will be dropped,
   166  // they would be mishandled down chain.
   167  //
   168  // The returned environment is sorted lexicographically by final variable name.
   169  func (env Environment) ForExecEscapeUnsafe(unsafeEscapePrefix string) []string {
   170  	raw := make([]string, 0, len(env))
   171  	keys := make([]string, 0, len(env))
   172  	escaped := 0
   173  	for key := range env {
   174  		if strings.HasPrefix(key, unsafeEscapePrefix) {
   175  			continue
   176  		}
   177  		if unsafeEnv[key] {
   178  			key = unsafeEscapePrefix + key
   179  			escaped += 1
   180  		}
   181  		keys = append(keys, key)
   182  	}
   183  	sort.Strings(keys)
   184  	var firstEscaped int
   185  	if escaped > 0 {
   186  		firstEscaped = sort.SearchStrings(keys, unsafeEscapePrefix)
   187  	}
   188  	for i, key := range keys {
   189  		envKey := key
   190  		if i >= firstEscaped && i < (firstEscaped+escaped) {
   191  			envKey = key[len(unsafeEscapePrefix):]
   192  		}
   193  		raw = append(raw, fmt.Sprintf("%s=%s", key, env[envKey]))
   194  	}
   195  	return raw
   196  }
   197  
   198  // ExpandableEnv represents alterations to an environment as ordered
   199  // key, value entries.
   200  //
   201  // Values can refer to predefined entries by using shell-like
   202  // syntax $KEY or ${KEY}.
   203  type ExpandableEnv struct {
   204  	*strutil.OrderedMap
   205  }
   206  
   207  // NewExpandableEnv returns a new expandable environment comprised of given pairs.
   208  func NewExpandableEnv(pairs ...string) ExpandableEnv {
   209  	return ExpandableEnv{OrderedMap: strutil.NewOrderedMap(pairs...)}
   210  }
   211  
   212  // ExtendWithExpanded extends the environment with eenv.
   213  //
   214  // Environment is modified in place. Each variable defined by eenv is
   215  // expanded according to os.Expand, using the environment itself as it
   216  // gets extended. Undefined variables expand to an empty string.
   217  func (env *Environment) ExtendWithExpanded(eenv ExpandableEnv) {
   218  	if *env == nil {
   219  		*env = make(Environment)
   220  	}
   221  
   222  	for _, key := range eenv.Keys() {
   223  		(*env)[key] = os.Expand(eenv.Get(key), func(varName string) string {
   224  			return (*env)[varName]
   225  		})
   226  	}
   227  }
   228  
   229  // unsafeEnv is a set of unsafe environment variables.
   230  //
   231  // Environment variables glibc strips out when running a setuid binary.
   232  // Taken from https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/generic/unsecvars.h;hb=HEAD
   233  // TODO: use go generate to obtain this list at build time.
   234  var unsafeEnv = map[string]bool{
   235  	"GCONV_PATH":       true,
   236  	"GETCONF_DIR":      true,
   237  	"GLIBC_TUNABLES":   true,
   238  	"HOSTALIASES":      true,
   239  	"LD_AUDIT":         true,
   240  	"LD_DEBUG":         true,
   241  	"LD_DEBUG_OUTPUT":  true,
   242  	"LD_DYNAMIC_WEAK":  true,
   243  	"LD_HWCAP_MASK":    true,
   244  	"LD_LIBRARY_PATH":  true,
   245  	"LD_ORIGIN_PATH":   true,
   246  	"LD_PRELOAD":       true,
   247  	"LD_PROFILE":       true,
   248  	"LD_SHOW_AUXV":     true,
   249  	"LD_USE_LOAD_BIAS": true,
   250  	"LOCALDOMAIN":      true,
   251  	"LOCPATH":          true,
   252  	"MALLOC_TRACE":     true,
   253  	"NIS_PATH":         true,
   254  	"NLSPATH":          true,
   255  	"RESOLV_HOST_CONF": true,
   256  	"RES_OPTIONS":      true,
   257  	"TMPDIR":           true,
   258  	"TZDIR":            true,
   259  }