mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
* strutil: add Environment, EnvironmentDelta and RawEnvironment This patch adds three new types and supporting methods. Environment is an unordered map representing environment entries that is convenient to modify. EnvironmentDelta is an ordered map representing changes to an environment that also supports variable substitution and expansion. Finally RawEnvironment is a slice representing key=value pairs that is useful for low-level system interfaces. Environment and EnvironmentDelta can be modified with simple map-like methods Get, Set, Del. RawEnvironment can only be parsed to an Environment. The patch also contains Environment.ApplyDelta function, which correctly applies a single delta to a given environment. The types are designed to avoid the mistake of appending two slices representing environment strings together. This was the original cause of bug https://bugs.launchpad.net/snapd/+bug/1860369 Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: use Environment, EnvironmentDelta and RawEnvironment This patch replaces all ad-hoc environment handling with the three aforementioned types. Environment is used whenever we are working with the real complete environment (starting from OSEnvironment). EnvironmentDelta is used to modify it according to snap-level and application-level or hook-level overrides. Finally RawEnvironment is only used by snap run and snap-exec, immediately before calling execve. This fixes incorrect handling of environment in snap-exec and also avoids this class of bugs by making it hard, due to type system, to express mistakes again. Fixes: https://bugs.launchpad.net/snapd/+bug/1860369 Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * osutil/env: import all symbols from check.v1 This style is shorter and we use it nearly everywhere else. I'm about to combine more tests into this file and I don't want to invert their style over to the verbose one. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: move new environment code to osutil There's a lot of churn but that's unavoidable in such a move. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: remove RawEnvironment type The raw environment is necessary only for executing programs. We can remove the type and replace it with []string in tests (where it has most of the usage) and a few call sites. The corresponding method from Environment was renamed to ForExec(). Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: make Environment a map[string]string Some extra methods such as Get, Set and Delete are retained but they are all arguably unnecessarily and will be removed in another pass. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: remove most map-like methods from Environment This includes Get, Delete and Contains but not Set. Set has extra semantics that will be removed in another pass. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: remove Environment.Set This patch removes the leftover map-like method from the Environment type. Some extra care was applied to ensure we don't try to write through a nil map, something that Set protected against. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * cmd/snap-exec: use NewEnvironment in test code Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: rename EnvironmentDelta to ExpandableEnv The main feature of what was called EnvironmentDelta is the fact it could expand expressions in variable definitions. This makes it more obvious. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: make ExpandableEnv an immutable *strutil.OrderedMap There's a lot of extra changes to make the journey more complete. First of all both snap.AppInfo and snap.HookInfo provide a method EnvStack() []osutil.ExpandableEnv which replaces the earlier EnvironmentOverrides. Instead of merging environment entries the entire chain is modeled. This will be relevant when we want to expand values. This also means that support code to make EnvStack mutable is gone. Second of all, snap/snapenv.ExecEnv has reverted to using plain Environment (aka map[string]string) for basicEnv and userEnv. The values never had expressions so using the more sophisticated code there is not really necessary. Delta from master could be reduced further but I went for clarity of the progression over the clarity of the end-to-end diff. Lastly, a small but important change, snap-exec now expands each ExpandableEnv in sequence. First for the snap-level and then for the app or hook level. This should fix the issue that each level wanted to expand but not completely erase the value set previously. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * osutil: remove NewEnvironment With the current type it's not any better than just defining a map locally. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * osutil: make ParseRawEnvironment private It is only required for OSEnvironment and is not used anywhere outside of the package. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: rename ApplyDelta to SetExpandableEnv Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * osutil: update stale documentation referring to delta Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * osutil: weaken SetExpandableEnv When initially implemented as a part of this branch, SetExpandableEnv (nee ApplyDelta) behaved in somewhat unusual and "strong" way, where forward references _were_ expanded. This was not previously so and arguably is not needed. As the code stands now, we have proper chaining starting from system environment, to snap-level environment declarations (that can expand variables) and all the way down to application or hook-level environment declarations (also with variable expansions). Since such declarations are ordered this provides the equivalence of shell execution performing the same expansions. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: special-case Environment.Transform Instead of offering specialized environment transformation functions offer just the things we need: escaping and un-escaping of unsafe variables removed by the dynamic linker when loading binaries with AT_SECURE flag. Various bits move from snap/snapenv to osutil. Signed-off-by: Zygmunt Krynicki <zygmunt.krynicki@canonical.com> * many: EnvStack to EnvChain, SetExpandableEnv to ExtendWithExpanded also adjust doc comments and cover ExtendWithExpanded for the nil initial env case * cmd/snap,snap/snapenv: change snapenv.ExecEnv into ExtendEnvForRun * many: make preserving ld.so setuid unsafe vars a boundary concern support the escaping/unescaping that we use for preserving vars stripped out by ld.so for snap-confine, via variants of OSEnvironment/ForExec so that is happens at the process boundaries, loading os env and setting env for exec add/change some tests related to this adjust comments/doc comments or add Co-authored-by: Samuele Pedroni <pedronis@lucediurna.net> Co-authored-by: Michael Vogt <mvo@ubuntu.com>
260 lines
7.5 KiB
Go
260 lines
7.5 KiB
Go
// -*- Mode: Go; indent-tabs-mode: t -*-
|
|
|
|
/*
|
|
* Copyright (C) 2016 Canonical Ltd
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 3 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
package osutil
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/snapcore/snapd/strutil"
|
|
)
|
|
|
|
// GetenvBool returns whether the given key may be considered "set" in the
|
|
// environment (i.e. it is set to one of "1", "true", etc).
|
|
//
|
|
// An optional second argument can be provided, which determines how to
|
|
// treat missing or unparsable values; default is to treat them as false.
|
|
func GetenvBool(key string, dflt ...bool) bool {
|
|
if val := strings.TrimSpace(os.Getenv(key)); val != "" {
|
|
if b, err := strconv.ParseBool(val); err == nil {
|
|
return b
|
|
}
|
|
}
|
|
|
|
if len(dflt) > 0 {
|
|
return dflt[0]
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetenvInt64 interprets the value of the given environment variable
|
|
// as an int64 and returns the corresponding value. The base can be
|
|
// implied via the prefix (0x for 16, 0 for 8; otherwise 10).
|
|
//
|
|
// An optional second argument can be provided, which determines how to
|
|
// treat missing or unparsable values; default is to treat them as 0.
|
|
func GetenvInt64(key string, dflt ...int64) int64 {
|
|
if val := strings.TrimSpace(os.Getenv(key)); val != "" {
|
|
if b, err := strconv.ParseInt(val, 0, 64); err == nil {
|
|
return b
|
|
}
|
|
}
|
|
|
|
if len(dflt) > 0 {
|
|
return dflt[0]
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// Environment is an unordered map of key=value strings.
|
|
//
|
|
// Environment can be manipulated with available methods and eventually
|
|
// converted to low-level representation necessary when executing programs.
|
|
// This approach discourages operations that could result in duplicate
|
|
// environment variable definitions from being constructed.
|
|
type Environment map[string]string
|
|
|
|
func parseEnvEntry(entry string) (string, string, error) {
|
|
parts := strings.SplitN(entry, "=", 2)
|
|
if len(parts) != 2 {
|
|
return "", "", fmt.Errorf("cannot parse environment entry: %q", entry)
|
|
}
|
|
key, value := parts[0], parts[1]
|
|
if key == "" {
|
|
return "", "", fmt.Errorf("environment variable name cannot be empty: %q", entry)
|
|
}
|
|
return key, value, nil
|
|
}
|
|
|
|
// parseRawEnvironment parsers raw environment.
|
|
//
|
|
// This function fails if any of the provided values are not in the form of
|
|
// key=value or if there are duplicate keys.
|
|
func parseRawEnvironment(raw []string) (Environment, error) {
|
|
env := make(Environment, len(raw))
|
|
for _, entry := range raw {
|
|
key, value, err := parseEnvEntry(entry)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if _, ok := env[key]; ok {
|
|
return nil, fmt.Errorf("cannot overwrite earlier value of %q", key)
|
|
}
|
|
env[key] = value
|
|
}
|
|
return env, nil
|
|
}
|
|
|
|
// OSEnvironment returns the environment of the calling process.
|
|
func OSEnvironment() (Environment, error) {
|
|
return parseRawEnvironment(os.Environ())
|
|
}
|
|
|
|
// OSEnvironmentUnescapeUnsafe returns the environment of the calling process.
|
|
// It will also strip unsafeEscapePrefix from any variable starting with it.
|
|
// Use-case/assumption is that ForExecEscapeUnsafe was used previously
|
|
// along the exec chain.
|
|
func OSEnvironmentUnescapeUnsafe(unsafeEscapePrefix string) (Environment, error) {
|
|
env, err := parseRawEnvironment(os.Environ())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for key, value := range env {
|
|
if newKey := strings.TrimPrefix(key, unsafeEscapePrefix); key != newKey {
|
|
delete(env, key)
|
|
if _, ok := env[newKey]; ok {
|
|
// assume newKey was originally
|
|
// dropped when the escaped key and
|
|
// value were set so current value is
|
|
// newer here, keep it
|
|
continue
|
|
}
|
|
env[newKey] = value
|
|
}
|
|
}
|
|
return env, nil
|
|
}
|
|
|
|
// ForExec returns the environment in a form suitable for using with
|
|
// the exec family of functions.
|
|
//
|
|
// The returned environment is sorted lexicographically by variable name.
|
|
func (env Environment) ForExec() []string {
|
|
raw := make([]string, 0, len(env))
|
|
keys := make([]string, 0, len(env))
|
|
for key := range env {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
raw = append(raw, fmt.Sprintf("%s=%s", key, env[key]))
|
|
}
|
|
return raw
|
|
}
|
|
|
|
// ForExecEscapeUnsafe returns the environment in a form suitable for
|
|
// using with the exec family of functions.
|
|
//
|
|
// Further variables that are usually stripped out by ld.so when starting a
|
|
// setuid process are renamed by prepending unsafeEscapePrefix to
|
|
// them.
|
|
//
|
|
// Unlikely variables already starting with the prefix will be dropped,
|
|
// they would be mishandled down chain.
|
|
//
|
|
// The returned environment is sorted lexicographically by final variable name.
|
|
func (env Environment) ForExecEscapeUnsafe(unsafeEscapePrefix string) []string {
|
|
raw := make([]string, 0, len(env))
|
|
keys := make([]string, 0, len(env))
|
|
escaped := 0
|
|
for key := range env {
|
|
if strings.HasPrefix(key, unsafeEscapePrefix) {
|
|
continue
|
|
}
|
|
if unsafeEnv[key] {
|
|
key = unsafeEscapePrefix + key
|
|
escaped += 1
|
|
}
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
var firstEscaped int
|
|
if escaped > 0 {
|
|
firstEscaped = sort.SearchStrings(keys, unsafeEscapePrefix)
|
|
}
|
|
for i, key := range keys {
|
|
envKey := key
|
|
if i >= firstEscaped && i < (firstEscaped+escaped) {
|
|
envKey = key[len(unsafeEscapePrefix):]
|
|
}
|
|
raw = append(raw, fmt.Sprintf("%s=%s", key, env[envKey]))
|
|
}
|
|
return raw
|
|
}
|
|
|
|
// ExpandableEnv represents alterations to an environment as ordered
|
|
// key, value entries.
|
|
//
|
|
// Values can refer to predefined entries by using shell-like
|
|
// syntax $KEY or ${KEY}.
|
|
type ExpandableEnv struct {
|
|
*strutil.OrderedMap
|
|
}
|
|
|
|
// NewExpandableEnv returns a new expandable environment comprised of given pairs.
|
|
func NewExpandableEnv(pairs ...string) ExpandableEnv {
|
|
return ExpandableEnv{OrderedMap: strutil.NewOrderedMap(pairs...)}
|
|
}
|
|
|
|
// ExtendWithExpanded extends the environment with eenv.
|
|
//
|
|
// Environment is modified in place. Each variable defined by eenv is
|
|
// expanded according to os.Expand, using the environment itself as it
|
|
// gets extended. Undefined variables expand to an empty string.
|
|
func (env *Environment) ExtendWithExpanded(eenv ExpandableEnv) {
|
|
if *env == nil {
|
|
*env = make(Environment)
|
|
}
|
|
|
|
for _, key := range eenv.Keys() {
|
|
(*env)[key] = os.Expand(eenv.Get(key), func(varName string) string {
|
|
return (*env)[varName]
|
|
})
|
|
}
|
|
}
|
|
|
|
// unsafeEnv is a set of unsafe environment variables.
|
|
//
|
|
// Environment variables glibc strips out when running a setuid binary.
|
|
// Taken from https://sourceware.org/git/?p=glibc.git;a=blob_plain;f=sysdeps/generic/unsecvars.h;hb=HEAD
|
|
// TODO: use go generate to obtain this list at build time.
|
|
var unsafeEnv = map[string]bool{
|
|
"GCONV_PATH": true,
|
|
"GETCONF_DIR": true,
|
|
"GLIBC_TUNABLES": true,
|
|
"HOSTALIASES": true,
|
|
"LD_AUDIT": true,
|
|
"LD_DEBUG": true,
|
|
"LD_DEBUG_OUTPUT": true,
|
|
"LD_DYNAMIC_WEAK": true,
|
|
"LD_HWCAP_MASK": true,
|
|
"LD_LIBRARY_PATH": true,
|
|
"LD_ORIGIN_PATH": true,
|
|
"LD_PRELOAD": true,
|
|
"LD_PROFILE": true,
|
|
"LD_SHOW_AUXV": true,
|
|
"LD_USE_LOAD_BIAS": true,
|
|
"LOCALDOMAIN": true,
|
|
"LOCPATH": true,
|
|
"MALLOC_TRACE": true,
|
|
"NIS_PATH": true,
|
|
"NLSPATH": true,
|
|
"RESOLV_HOST_CONF": true,
|
|
"RES_OPTIONS": true,
|
|
"TMPDIR": true,
|
|
"TZDIR": true,
|
|
}
|