uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
305
nixos/modules/security/wrappers/default.nix
Normal file
305
nixos/modules/security/wrappers/default.nix
Normal file
|
|
@ -0,0 +1,305 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
|
||||
inherit (config.security) wrapperDir wrappers;
|
||||
|
||||
parentWrapperDir = dirOf wrapperDir;
|
||||
|
||||
securityWrapper = pkgs.callPackage ./wrapper.nix {
|
||||
inherit parentWrapperDir;
|
||||
};
|
||||
|
||||
fileModeType =
|
||||
let
|
||||
# taken from the chmod(1) man page
|
||||
symbolic = "[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+";
|
||||
numeric = "[-+=]?[0-7]{0,4}";
|
||||
mode = "((${symbolic})(,${symbolic})*)|(${numeric})";
|
||||
in
|
||||
lib.types.strMatching mode
|
||||
// { description = "file mode string"; };
|
||||
|
||||
wrapperType = lib.types.submodule ({ name, config, ... }: {
|
||||
options.source = lib.mkOption
|
||||
{ type = lib.types.path;
|
||||
description = "The absolute path to the program to be wrapped.";
|
||||
};
|
||||
options.program = lib.mkOption
|
||||
{ type = with lib.types; nullOr str;
|
||||
default = name;
|
||||
description = ''
|
||||
The name of the wrapper program. Defaults to the attribute name.
|
||||
'';
|
||||
};
|
||||
options.owner = lib.mkOption
|
||||
{ type = lib.types.str;
|
||||
description = "The owner of the wrapper program.";
|
||||
};
|
||||
options.group = lib.mkOption
|
||||
{ type = lib.types.str;
|
||||
description = "The group of the wrapper program.";
|
||||
};
|
||||
options.permissions = lib.mkOption
|
||||
{ type = fileModeType;
|
||||
default = "u+rx,g+x,o+x";
|
||||
example = "a+rx";
|
||||
description = ''
|
||||
The permissions of the wrapper program. The format is that of a
|
||||
symbolic or numeric file mode understood by <command>chmod</command>.
|
||||
'';
|
||||
};
|
||||
options.capabilities = lib.mkOption
|
||||
{ type = lib.types.commas;
|
||||
default = "";
|
||||
description = ''
|
||||
A comma-separated list of capabilities to be given to the wrapper
|
||||
program. For capabilities supported by the system check the
|
||||
<citerefentry>
|
||||
<refentrytitle>capabilities</refentrytitle>
|
||||
<manvolnum>7</manvolnum>
|
||||
</citerefentry>
|
||||
manual page.
|
||||
|
||||
<note><para>
|
||||
<literal>cap_setpcap</literal>, which is required for the wrapper
|
||||
program to be able to raise caps into the Ambient set is NOT raised
|
||||
to the Ambient set so that the real program cannot modify its own
|
||||
capabilities!! This may be too restrictive for cases in which the
|
||||
real program needs cap_setpcap but it at least leans on the side
|
||||
security paranoid vs. too relaxed.
|
||||
</para></note>
|
||||
'';
|
||||
};
|
||||
options.setuid = lib.mkOption
|
||||
{ type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to add the setuid bit the wrapper program.";
|
||||
};
|
||||
options.setgid = lib.mkOption
|
||||
{ type = lib.types.bool;
|
||||
default = false;
|
||||
description = "Whether to add the setgid bit the wrapper program.";
|
||||
};
|
||||
});
|
||||
|
||||
###### Activation script for the setcap wrappers
|
||||
mkSetcapProgram =
|
||||
{ program
|
||||
, capabilities
|
||||
, source
|
||||
, owner
|
||||
, group
|
||||
, permissions
|
||||
, ...
|
||||
}:
|
||||
''
|
||||
cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
|
||||
echo -n "${source}" > "$wrapperDir/${program}.real"
|
||||
|
||||
# Prevent races
|
||||
chmod 0000 "$wrapperDir/${program}"
|
||||
chown ${owner}:${group} "$wrapperDir/${program}"
|
||||
|
||||
# Set desired capabilities on the file plus cap_setpcap so
|
||||
# the wrapper program can elevate the capabilities set on
|
||||
# its file into the Ambient set.
|
||||
${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" "$wrapperDir/${program}"
|
||||
|
||||
# Set the executable bit
|
||||
chmod ${permissions} "$wrapperDir/${program}"
|
||||
'';
|
||||
|
||||
###### Activation script for the setuid wrappers
|
||||
mkSetuidProgram =
|
||||
{ program
|
||||
, source
|
||||
, owner
|
||||
, group
|
||||
, setuid
|
||||
, setgid
|
||||
, permissions
|
||||
, ...
|
||||
}:
|
||||
''
|
||||
cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
|
||||
echo -n "${source}" > "$wrapperDir/${program}.real"
|
||||
|
||||
# Prevent races
|
||||
chmod 0000 "$wrapperDir/${program}"
|
||||
chown ${owner}:${group} "$wrapperDir/${program}"
|
||||
|
||||
chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" "$wrapperDir/${program}"
|
||||
'';
|
||||
|
||||
mkWrappedPrograms =
|
||||
builtins.map
|
||||
(opts:
|
||||
if opts.capabilities != ""
|
||||
then mkSetcapProgram opts
|
||||
else mkSetuidProgram opts
|
||||
) (lib.attrValues wrappers);
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(lib.mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead")
|
||||
(lib.mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead")
|
||||
];
|
||||
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
security.wrappers = lib.mkOption {
|
||||
type = lib.types.attrsOf wrapperType;
|
||||
default = {};
|
||||
example = lib.literalExpression
|
||||
''
|
||||
{
|
||||
# a setuid root program
|
||||
doas =
|
||||
{ setuid = true;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
source = "''${pkgs.doas}/bin/doas";
|
||||
};
|
||||
|
||||
# a setgid program
|
||||
locate =
|
||||
{ setgid = true;
|
||||
owner = "root";
|
||||
group = "mlocate";
|
||||
source = "''${pkgs.locate}/bin/locate";
|
||||
};
|
||||
|
||||
# a program with the CAP_NET_RAW capability
|
||||
ping =
|
||||
{ owner = "root";
|
||||
group = "root";
|
||||
capabilities = "cap_net_raw+ep";
|
||||
source = "''${pkgs.iputils.out}/bin/ping";
|
||||
};
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
This option effectively allows adding setuid/setgid bits, capabilities,
|
||||
changing file ownership and permissions of a program without directly
|
||||
modifying it. This works by creating a wrapper program under the
|
||||
<option>security.wrapperDir</option> directory, which is then added to
|
||||
the shell <literal>PATH</literal>.
|
||||
'';
|
||||
};
|
||||
|
||||
security.wrapperDir = lib.mkOption {
|
||||
type = lib.types.path;
|
||||
default = "/run/wrappers/bin";
|
||||
internal = true;
|
||||
description = ''
|
||||
This option defines the path to the wrapper programs. It
|
||||
should not be overriden.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
config = {
|
||||
|
||||
assertions = lib.mapAttrsToList
|
||||
(name: opts:
|
||||
{ assertion = opts.setuid || opts.setgid -> opts.capabilities == "";
|
||||
message = ''
|
||||
The security.wrappers.${name} wrapper is not valid:
|
||||
setuid/setgid and capabilities are mutually exclusive.
|
||||
'';
|
||||
}
|
||||
) wrappers;
|
||||
|
||||
security.wrappers =
|
||||
let
|
||||
mkSetuidRoot = source:
|
||||
{ setuid = true;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
inherit source;
|
||||
};
|
||||
in
|
||||
{ # These are mount related wrappers that require the +s permission.
|
||||
fusermount = mkSetuidRoot "${pkgs.fuse}/bin/fusermount";
|
||||
fusermount3 = mkSetuidRoot "${pkgs.fuse3}/bin/fusermount3";
|
||||
mount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/mount";
|
||||
umount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/umount";
|
||||
};
|
||||
|
||||
boot.specialFileSystems.${parentWrapperDir} = {
|
||||
fsType = "tmpfs";
|
||||
options = [ "nodev" "mode=755" ];
|
||||
};
|
||||
|
||||
# Make sure our wrapperDir exports to the PATH env variable when
|
||||
# initializing the shell
|
||||
environment.extraInit = ''
|
||||
# Wrappers override other bin directories.
|
||||
export PATH="${wrapperDir}:$PATH"
|
||||
'';
|
||||
|
||||
security.apparmor.includes."nixos/security.wrappers" = ''
|
||||
include "${pkgs.apparmorRulesFromClosure { name="security.wrappers"; } [
|
||||
securityWrapper
|
||||
]}"
|
||||
'';
|
||||
|
||||
###### wrappers activation script
|
||||
system.activationScripts.wrappers =
|
||||
lib.stringAfter [ "specialfs" "users" ]
|
||||
''
|
||||
chmod 755 "${parentWrapperDir}"
|
||||
|
||||
# We want to place the tmpdirs for the wrappers to the parent dir.
|
||||
wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
|
||||
chmod a+rx "$wrapperDir"
|
||||
|
||||
${lib.concatStringsSep "\n" mkWrappedPrograms}
|
||||
|
||||
if [ -L ${wrapperDir} ]; then
|
||||
# Atomically replace the symlink
|
||||
# See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
|
||||
old=$(readlink -f ${wrapperDir})
|
||||
if [ -e "${wrapperDir}-tmp" ]; then
|
||||
rm --force --recursive "${wrapperDir}-tmp"
|
||||
fi
|
||||
ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp"
|
||||
mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}"
|
||||
rm --force --recursive "$old"
|
||||
else
|
||||
# For initial setup
|
||||
ln --symbolic "$wrapperDir" "${wrapperDir}"
|
||||
fi
|
||||
'';
|
||||
|
||||
###### wrappers consistency checks
|
||||
system.extraDependencies = lib.singleton (pkgs.runCommandLocal
|
||||
"ensure-all-wrappers-paths-exist" { }
|
||||
''
|
||||
# make sure we produce output
|
||||
mkdir -p $out
|
||||
|
||||
echo -n "Checking that Nix store paths of all wrapped programs exist... "
|
||||
|
||||
declare -A wrappers
|
||||
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v:
|
||||
"wrappers['${n}']='${v.source}'") wrappers)}
|
||||
|
||||
for name in "''${!wrappers[@]}"; do
|
||||
path="''${wrappers[$name]}"
|
||||
if [[ "$path" =~ /nix/store ]] && [ ! -e "$path" ]; then
|
||||
test -t 1 && echo -ne '\033[1;31m'
|
||||
echo "FAIL"
|
||||
echo "The path $path does not exist!"
|
||||
echo 'Please, check the value of `security.wrappers."'$name'".source`.'
|
||||
test -t 1 && echo -ne '\033[0m'
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "OK"
|
||||
'');
|
||||
};
|
||||
}
|
||||
237
nixos/modules/security/wrappers/wrapper.c
Normal file
237
nixos/modules/security/wrappers/wrapper.c
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <stdnoreturn.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <linux/capability.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <syscall.h>
|
||||
#include <byteswap.h>
|
||||
|
||||
#define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr))
|
||||
|
||||
extern char **environ;
|
||||
|
||||
// The WRAPPER_DIR macro is supplied at compile time so that it cannot
|
||||
// be changed at runtime
|
||||
static char *wrapper_dir = WRAPPER_DIR;
|
||||
|
||||
// Wrapper debug variable name
|
||||
static char *wrapper_debug = "WRAPPER_DEBUG";
|
||||
|
||||
#define CAP_SETPCAP 8
|
||||
|
||||
#if __BYTE_ORDER == __BIG_ENDIAN
|
||||
#define LE32_TO_H(x) bswap_32(x)
|
||||
#else
|
||||
#define LE32_TO_H(x) (x)
|
||||
#endif
|
||||
|
||||
static noreturn void assert_failure(const char *assertion) {
|
||||
fprintf(stderr, "Assertion `%s` in NixOS's wrapper.c failed.\n", assertion);
|
||||
fflush(stderr);
|
||||
abort();
|
||||
}
|
||||
|
||||
int get_last_cap(unsigned *last_cap) {
|
||||
FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r");
|
||||
if (file == NULL) {
|
||||
int saved_errno = errno;
|
||||
fprintf(stderr, "failed to open /proc/sys/kernel/cap_last_cap: %s\n", strerror(errno));
|
||||
return -saved_errno;
|
||||
}
|
||||
int res = fscanf(file, "%u", last_cap);
|
||||
if (res == EOF) {
|
||||
int saved_errno = errno;
|
||||
fprintf(stderr, "could not read number from /proc/sys/kernel/cap_last_cap: %s\n", strerror(errno));
|
||||
return -saved_errno;
|
||||
}
|
||||
fclose(file);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Given the path to this program, fetch its configured capability set
|
||||
// (as set by `setcap ... /path/to/file`) and raise those capabilities
|
||||
// into the Ambient set.
|
||||
static int make_caps_ambient(const char *self_path) {
|
||||
struct vfs_ns_cap_data data = {};
|
||||
int r = getxattr(self_path, "security.capability", &data, sizeof(data));
|
||||
|
||||
if (r < 0) {
|
||||
if (errno == ENODATA) {
|
||||
// no capabilities set
|
||||
return 0;
|
||||
}
|
||||
fprintf(stderr, "cannot get capabilities for %s: %s", self_path, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t size;
|
||||
uint32_t version = LE32_TO_H(data.magic_etc) & VFS_CAP_REVISION_MASK;
|
||||
switch (version) {
|
||||
case VFS_CAP_REVISION_1:
|
||||
size = VFS_CAP_U32_1;
|
||||
break;
|
||||
case VFS_CAP_REVISION_2:
|
||||
case VFS_CAP_REVISION_3:
|
||||
size = VFS_CAP_U32_3;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "BUG! Unsupported capability version 0x%x on %s. Report to NixOS bugtracker\n", version, self_path);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const struct __user_cap_header_struct header = {
|
||||
.version = _LINUX_CAPABILITY_VERSION_3,
|
||||
.pid = getpid(),
|
||||
};
|
||||
struct __user_cap_data_struct user_data[2] = {};
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
// merge inheritable & permitted into one
|
||||
user_data[i].permitted = user_data[i].inheritable =
|
||||
LE32_TO_H(data.data[i].inheritable) | LE32_TO_H(data.data[i].permitted);
|
||||
}
|
||||
|
||||
if (syscall(SYS_capset, &header, &user_data) < 0) {
|
||||
fprintf(stderr, "failed to inherit capabilities: %s", strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
unsigned last_cap;
|
||||
r = get_last_cap(&last_cap);
|
||||
if (r < 0) {
|
||||
return 1;
|
||||
}
|
||||
uint64_t set = user_data[0].permitted | (uint64_t)user_data[1].permitted << 32;
|
||||
for (unsigned cap = 0; cap < last_cap; cap++) {
|
||||
if (!(set & (1ULL << cap))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for the cap_setpcap capability, we set this on the
|
||||
// wrapper so it can elevate the capabilities to the Ambient
|
||||
// set but we do not want to propagate it down into the
|
||||
// wrapped program.
|
||||
//
|
||||
// TODO: what happens if that's the behavior you want
|
||||
// though???? I'm preferring a strict vs. loose policy here.
|
||||
if (cap == CAP_SETPCAP) {
|
||||
if(getenv(wrapper_debug)) {
|
||||
fprintf(stderr, "cap_setpcap in set, skipping it\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, (unsigned long) cap, 0, 0)) {
|
||||
fprintf(stderr, "cannot raise the capability %d into the ambient set: %s\n", cap, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if (getenv(wrapper_debug)) {
|
||||
fprintf(stderr, "raised %d into the ambient capability set\n", cap);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readlink_malloc(const char *p, char **ret) {
|
||||
size_t l = FILENAME_MAX+1;
|
||||
int r;
|
||||
|
||||
for (;;) {
|
||||
char *c = calloc(l, sizeof(char));
|
||||
if (!c) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ssize_t n = readlink(p, c, l-1);
|
||||
if (n < 0) {
|
||||
r = -errno;
|
||||
free(c);
|
||||
return r;
|
||||
}
|
||||
|
||||
if ((size_t) n < l-1) {
|
||||
c[n] = 0;
|
||||
*ret = c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
free(c);
|
||||
l *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
ASSERT(argc >= 1);
|
||||
char *self_path = NULL;
|
||||
int self_path_size = readlink_malloc("/proc/self/exe", &self_path);
|
||||
if (self_path_size < 0) {
|
||||
fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size));
|
||||
}
|
||||
|
||||
// Make sure that we are being executed from the right location,
|
||||
// i.e., `safe_wrapper_dir'. This is to prevent someone from creating
|
||||
// hard link `X' from some other location, along with a false
|
||||
// `X.real' file, to allow arbitrary programs from being executed
|
||||
// with elevated capabilities.
|
||||
int len = strlen(wrapper_dir);
|
||||
if (len > 0 && '/' == wrapper_dir[len - 1])
|
||||
--len;
|
||||
ASSERT(!strncmp(self_path, wrapper_dir, len));
|
||||
ASSERT('/' == wrapper_dir[0]);
|
||||
ASSERT('/' == self_path[len]);
|
||||
|
||||
// Make *really* *really* sure that we were executed as
|
||||
// `self_path', and not, say, as some other setuid program. That
|
||||
// is, our effective uid/gid should match the uid/gid of
|
||||
// `self_path'.
|
||||
struct stat st;
|
||||
ASSERT(lstat(self_path, &st) != -1);
|
||||
|
||||
ASSERT(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid()));
|
||||
ASSERT(!(st.st_mode & S_ISGID) || (st.st_gid == getegid()));
|
||||
|
||||
// And, of course, we shouldn't be writable.
|
||||
ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH)));
|
||||
|
||||
// Read the path of the real (wrapped) program from <self>.real.
|
||||
char real_fn[PATH_MAX + 10];
|
||||
int real_fn_size = snprintf(real_fn, sizeof(real_fn), "%s.real", self_path);
|
||||
ASSERT(real_fn_size < sizeof(real_fn));
|
||||
|
||||
int fd_self = open(real_fn, O_RDONLY);
|
||||
ASSERT(fd_self != -1);
|
||||
|
||||
char source_prog[PATH_MAX];
|
||||
len = read(fd_self, source_prog, PATH_MAX);
|
||||
ASSERT(len != -1);
|
||||
ASSERT(len < sizeof(source_prog));
|
||||
ASSERT(len > 0);
|
||||
source_prog[len] = 0;
|
||||
|
||||
close(fd_self);
|
||||
|
||||
// Read the capabilities set on the wrapper and raise them in to
|
||||
// the ambient set so the program we're wrapping receives the
|
||||
// capabilities too!
|
||||
if (make_caps_ambient(self_path) != 0) {
|
||||
free(self_path);
|
||||
return 1;
|
||||
}
|
||||
free(self_path);
|
||||
|
||||
execve(source_prog, argv, environ);
|
||||
|
||||
fprintf(stderr, "%s: cannot run `%s': %s\n",
|
||||
argv[0], source_prog, strerror(errno));
|
||||
|
||||
return 1;
|
||||
}
|
||||
21
nixos/modules/security/wrappers/wrapper.nix
Normal file
21
nixos/modules/security/wrappers/wrapper.nix
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{ stdenv, linuxHeaders, parentWrapperDir, debug ? false }:
|
||||
# For testing:
|
||||
# $ nix-build -E 'with import <nixpkgs> {}; pkgs.callPackage ./wrapper.nix { parentWrapperDir = "/run/wrappers"; debug = true; }'
|
||||
stdenv.mkDerivation {
|
||||
name = "security-wrapper";
|
||||
buildInputs = [ linuxHeaders ];
|
||||
dontUnpack = true;
|
||||
hardeningEnable = [ "pie" ];
|
||||
CFLAGS = [
|
||||
''-DWRAPPER_DIR="${parentWrapperDir}"''
|
||||
] ++ (if debug then [
|
||||
"-Werror" "-Og" "-g"
|
||||
] else [
|
||||
"-Wall" "-O2"
|
||||
]);
|
||||
dontStrip = debug;
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
$CC $CFLAGS ${./wrapper.c} -o $out/bin/security-wrapper
|
||||
'';
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue