mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-24 23:59:08 +02:00
* [client] Export the only-owner-writable path check from elevate
Pure refactor, no behavior change: the existing checkOnlyOwnerWritable gets a
thin exported wrapper so callers outside the elevation path can reuse it. No
call site changes here.
* [client] Validate the saved service parameters before applying them
The install reads <stateDir>/service.json and applies it to the service it then
registers: its arguments, its config path and its environment. The restricted
ACL that saveServiceParams puts on the state directory is applied when the file
is written, which is not necessarily before the file is first read, so the
install now checks the file rather than assuming it.
A file whose ownership or permissions are not the ones saveServiceParams
produces is treated as absent, and the install proceeds with its defaults. The
check covers the directories above the file as well, so what is checked is what
is read.
* [client] Restrict which environment variables the service is registered with
--service-env, and the service.json it persists to, accepted any name. A small
set of them decides how a process resolves the executables and libraries it
loads, and the daemon needs none of those: it now refuses them when they are
passed explicitly, and drops them with a warning when they come back from a
service.json written by an older version, so an upgrade does not fail over a
variable nobody needs.
* [client] Resolve netsh by absolute path
The lookup consulted PATH first and fell back to System32, in both the copy the
userspace firewall uses and the one that tears the interface down. It now asks
Windows for the system directory, so the resolution no longer depends on the
environment the service happens to be started with.
* [client] Move the System32 lookup into a package both callers share
Pure refactor, no behavior change: client/iface and client/firewall/uspfilter
carried a copy each of the same function, and neither imports the other, so the
body moves to client/internal/wincmd — alongside winregistry, which is where
the client's other Windows-only helper already lives. Both call sites now read
wincmd.System32("netsh").
* [client] Cover the System32 lookup with a test
Asserts what the previous commits changed: the lookup is absolute, and neither
PATH nor %SystemRoot% moves it.
* [client] Refuse the loader environment families by prefix
Review follow-up on the previous commit:
- LD_* and DYLD_* are now refused whole rather than name by name. Their members
differ per platform and libc and grow with new OS releases, so a list of them
is out of date as soon as it is written — DYLD_FALLBACK_LIBRARY_PATH and
DYLD_FALLBACK_FRAMEWORK_PATH were already missing from it.
- The names are folded to upper case only on Windows, where a variable is the
same one however it is spelled. Elsewhere the environment is case-sensitive,
so Path and PATH are two variables and only the exact spelling is the one that
is read; the fold refused the wrong one.
- TEMP and TMP stay in the denylist, but the rationale and the message now say
what they actually decide: where the service writes, not what it loads.
52 lines
1.9 KiB
Go
52 lines
1.9 KiB
Go
package elevate
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// CheckOnlyOwnerWritable reports an error unless path, and every directory
|
|
// leading to it, is owned by an account that can already act with the privileges
|
|
// the caller holds, and is writable by nobody else.
|
|
//
|
|
// Exported for callers outside elevation that read a file while privileged and
|
|
// then act on what it says: the same question this package asks of an
|
|
// executable, asked of a configuration file.
|
|
func CheckOnlyOwnerWritable(path string) error {
|
|
return checkOnlyOwnerWritable(path)
|
|
}
|
|
|
|
// trustedSelf returns the path of this executable, provided it is one we are
|
|
// willing to have run as root.
|
|
//
|
|
// The check is what keeps elevation from becoming a way to launder someone
|
|
// else's code into a root process: the user consents to NetBird being elevated,
|
|
// having been shown NetBird's name, so what runs must be the file NetBird was
|
|
// installed as and not something a third party could have swapped for it. An
|
|
// executable only its owner can write is that; anything wider is refused, and
|
|
// the caller falls back to showing the command instead.
|
|
//
|
|
// The owner writing to their own executable is not part of that threat: code
|
|
// running as the user can already prompt them for anything, and could just as
|
|
// well ask them to run the command by hand. What matters is that no *other*
|
|
// unprivileged account can reach it.
|
|
func trustedSelf() (string, error) {
|
|
exe, err := os.Executable()
|
|
if err != nil {
|
|
return "", fmt.Errorf("locate this executable: %w", err)
|
|
}
|
|
|
|
// Resolve symlinks so the checks below apply to the file that would actually
|
|
// be executed, not to a link somebody else may control.
|
|
resolved, err := filepath.EvalSymlinks(exe)
|
|
if err != nil {
|
|
return "", fmt.Errorf("resolve %s: %w", exe, err)
|
|
}
|
|
|
|
if err := checkOnlyOwnerWritable(resolved); err != nil {
|
|
return "", fmt.Errorf("%w: %s cannot be trusted to run as root: %w", ErrUnavailable, resolved, err)
|
|
}
|
|
return resolved, nil
|
|
}
|