-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Encryption key providers in general and keylocation=exec:// specifically #11731
base: master
Are you sure you want to change the base?
Conversation
d9214cd
to
ae10089
Compare
Not a clue so as to why why tests-sanity fails:
appears unrelated, since I didn't touch that bit, similarly zloop (which doesn't fail every time, as a bonus). buildbot/kernel.org fails because the kernel's too fresh, which I also (obviously) didn't touch. |
According to this log, the FreeBSD build fails because it can't link to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a zfs property that will automatically execute commands seems a bit risky to me. Maybe making it opt in via a zfs-load-key
flag would be safer? I'm not sure.
man/man8/zfs-load-key.8
Outdated
#!/bin/sh -ue | ||
|
||
if [ -z "$2" ]; then | ||
echo "No dataset name (zfs-create?)" >&2 | ||
exit 2 | ||
fi | ||
|
||
first="$(zfs get -Hpo value 'xyz.nabijaczleweli:bad-provider.salt' "$2")" | ||
second="$(zfs get -Hpo value 'xyz.nabijaczleweli:bad-provider.saltmp' "$2")" | ||
|
||
|
||
hash() { | ||
pass="" | ||
while [ -z "$pass" ]; do | ||
read -rp "Passphrase: " pass | ||
done | ||
|
||
if command -v sha256 >/dev/null; then | ||
sha256 -qs "$pass$1" | ||
else | ||
echo -n "$pass$1" | sha256sum | awk '{print $1}' | ||
fi | ||
} | ||
|
||
|
||
case "$1" in | ||
load) | ||
if [ "$first" = "-" ] && [ "$second" != "-" ]; then | ||
zfs set 'xyz.nabijaczleweli:bad-provider.salt'="$first" "$2" | ||
zfs inherit 'xyz.nabijaczleweli:bad-provider.saltmp' "$2" | ||
first="$second" | ||
second="-" | ||
elif [ "$first" = "-" ] && [ "$second" = "-" ]; then | ||
echo "No state?" >&2 | ||
exit 3 | ||
elif [ "$first" != "-" ] && [ "$second" != "-" ]; then | ||
which="" | ||
while [ "$which" != "f" ] && [ "$which" != "s" ]; do | ||
read -rp "Both states present! Select which one to use [fs]: " which | ||
done | ||
if [ "$which" = "f" ]; then | ||
echo "If this is the right key, run '$0 shift $2' afterward" | ||
else | ||
echo "If this is the right key, run '$0 cancel $2' afterward" | ||
first="$second" | ||
fi | ||
second="-" | ||
fi | ||
|
||
hash "$first" >&3 | ||
;; | ||
new) | ||
if [ "$second" != "-" ]; then | ||
echo "Second slot occupied? Run 'zfs load-key [-n] $2' to resolve this." >&2 | ||
exit 4 | ||
fi | ||
|
||
second="$(tr -cd '[:alnum:]' < /dev/urandom | dd bs=128 count=1 status=none)" | ||
zfs set 'xyz.nabijaczleweli:bad-provider.saltmp'="$second" "$2" | ||
|
||
hash "$second" >&3 | ||
;; | ||
shift) | ||
if [ "$first" != "-" ]; then | ||
zfs inherit 'xyz.nabijaczleweli:bad-provider.salt' "$2" | ||
first="-" | ||
fi | ||
|
||
zfs set 'xyz.nabijaczleweli:bad-provider.salt'="$second" "$2" | ||
zfs inherit 'xyz.nabijaczleweli:bad-provider.saltmp' "$2" | ||
first="$second" | ||
second="-" | ||
;; | ||
unshift) | ||
if [ "$second" != "-" ]; then | ||
zfs inherit 'xyz.nabijaczleweli:bad-provider.saltmp' "$2" | ||
second="-" | ||
fi | ||
|
||
zfs set 'xyz.nabijaczleweli:bad-provider.saltmp'="$first" "$2" | ||
zfs inherit 'xyz.nabijaczleweli:bad-provider.salt' "$2" | ||
second="$first" | ||
first="-" | ||
;; | ||
cancel) | ||
if [ "$second" != "-" ]; then | ||
zfs inherit 'xyz.nabijaczleweli:bad-provider.saltmp' "$2" | ||
second="-" | ||
fi | ||
;; | ||
*) | ||
echo "Unknown op $1" >&2 | ||
exit 5 | ||
;; | ||
esac |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure a 100 line code example fits in the man page. (Note however that I'm also not a zfs maintainer or dev)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I'm quite sure it doesn't, but I wasn't sure where to put it so it made sense. I hope to be actionably told off by a maintainer to use some specific other useful place.
man/man8/zfsprops.8
Outdated
specified absolute file path. If an exec URI is selected, the key will be | ||
acquired from the executable specified (see EXEC:// in | ||
.Xr zfs-load-key 8 ) | ||
for details. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From reading the two man pages, it's not immediately clear to me, unless I also read the code examples, that this command receives actions via arguments passed to it. I think the limitation that this can only take a single executable and not additional flags for the command should be documented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Granted, it's at the end of that list, but at the beginning of the sexion, I'm not sure how I'd make it clearer?
The provider is invoked with its full path as its zeroeth argument, the operation (lower-case) as its first argument, and the dataset name, if any, as its third argument.
I mean, it's not like a path can contain flags, is it?
About freebsd linking, I found https://bugs.webkit.org/show_bug.cgi?id=138420 and https://lists.freebsd.org/pipermail/freebsd-current/2018-December/072542.html . The first one points at |
7641b21
to
38685b7
Compare
Ugh, turns out |
de4ce9b
to
4580dd6
Compare
4580dd6
to
eba4260
Compare
I'm a bit dubious that this is the right approach, partly for usability reasons (that manpage is a real mouthful IMHO) but overwhelmingly for security reasons. The implication that a tampered or unverified pool can implicitly choose to run arbitrary commands on a host system feels like it's going to be a problem. |
If you have a better (either more resilient or simpler+as resilient) design, I'd love to hear it :) As for "the standard is a real mouthful" – sure, and? I fail to see how that's bad. I don't think I've left a damned thing underspecified. (If you've only read it in the GitHub view, I implore you to use an actual manual pager, it's much less annoying there.) I mean, I haven't really considered tampered-with pools to be a thing worth worrying about (especially given that all you get is |
if you're sure that having a phrase-generating program specified on the dataset/pool itself is really what you want. |
Updated with security considerations sexion and |
fae8a09
to
f63d581
Compare
zedlets have this handled already, that should be reused. |
f63d581
to
9dad586
Compare
Rebased on |
9dad586
to
e5328bd
Compare
Rebased and cleaned up a bit; needs test-suite entries, but should otherwise be good to review |
cfbf9a5
to
88fc42c
Compare
Updated to only take |
b6eb689
to
e5cbd50
Compare
8f51200
to
39e0cfb
Compare
This acquires the key material by running the file specified by the URI with [path, op, fsname], and reading back data from the pipe located at fd 3 after the child exits (this means, that, i.a., children that write Too Much get an I/O error instead of hanging, exec:// providers can be written in any language that can write(3, [buf]), and they can be as interactive (or non-interactive) and as verbose (or terse) as they want) See zfs-change-key(8) for example statesome providers, or the abomination below for a trivial stateless one #!/bin/sh -x echo "$0" "$@" [ -z "$2" ] && { echo "No dataset name (zfs-create?)" >&2 exit 1 } if command -v sha256 >/dev/null; then sha256 -qs "$2" else echo -n "$2" | sha256sum | awk '{print $1}' fi | tee /dev/stderr >&3 See zfs-change-key(8) for a user-level description of key-providers, or below for state machines load: * [_ _] => error * [_ x] => [x _], unseal(x) * [o x] => + show error + let user choose to try either one or the other state + instruct what to invoke in either case * [o _] => unseal(o) new: into staging area * fresh : [_ _] => [_ x] * regenerating: [o _] => [o x] * dirty: [? x] => shift (on success): mark new state as current, free old state * [_ _] => how? * [_ x] => [x _] * [o x] => [x _], free(o) * [o _] => [_ _], free(o) i.e. [a b] => [b _], free(a) unshift (on deletion): move current state to new * [_ _] => how? * [_ x] => wrong * [o x] => wrong * [o _] => [_ o] i.e. [a b] => [_ a] (technically free(b), i guess, but shouldn't happen) cancel (on error): free new state if present * how? : [_ _] => * from new : [_ x] => [_ _] free(x) * from new : [o x] => [o _] free(x) * from inherit/other executable: [o _] => [o _] i.e. [a, b] => [a, _], free(b) two stable states: [_ _] -> new: [_ n] --ok----> shift : [n _] [_ _] -> new: [_ n] --error-> cancel: [n _] [o _] -> new: [o n] --ok----> shift : [n _], free(o) [o _] -> new: [o n] --error-> cancel: [o _], free(n) [o _] (inheriting) --ok----> shift : [_ _], free(o) [o _] (inheriting) --error-> cancel: [o _] inheriting homomorphic to switching to something else [o _] -> unshift: [_ o] --ok----> cancel: [_ _], free(o) [o _] -> unshift: [_ o] --error-> shift : [o _] if shift or cancel wasn't called: [_ o] -> load -> [o, _], unseal [ó o] -> load -> pick a resolution. should allow loading either to check with zfs load-key -n and instruct what to do in either case [_ o] -> new -> error? try to load first, and pick a resolution [ó o] -> new -> error? try to load first, and pick a resolution i.e. somehow libzfs failed to do the shift after committing Signed-off-by: Ahelenia Ziemiańska <nabijaczleweli@nabijaczleweli.xyz>
39e0cfb
to
dd666a7
Compare
I echo the concerns of a dataset having defined a malicious key executable, which to me feels like a confused deputy CVE waiting to happen. My scenario is a bit different:
To quote @nabijaczleweli:
This rings true to me: needing to iterate over all the datasets of all the pools to find encrypted datasets, collect the unique set of An alternative I'd like is:
This avoids the confused deputy by requiring the caller to specify the program, allows for robustly integrating with ZFS's decryption mechanism without reimplementing a significant amount of work, and leaves the implementation up to the user. I hacked together an example based on this PR: grahamc@a176913 |
Given a `password.sh`: ```sh promptpass "$1" >&3 ``` prompt for each password with: ``` zfs load-key -e ./password.sh -a ``` Most of the complicated parts of this code are lifted from openzfs#11731 Co-authored-by: <nabijaczleweli@nabijaczleweli.xyz>
Motivation and Context
I previously babbled about this at length in #11707, but: to do anything past "password" or "file", you need a keylocation=prompt+keyformat=raw, a custom toolchain reimplementing parts of the zfs(8) API badly, and to essentially hijack the boot scripts. This Blows Massively™.
Description
See manpage and individual commit messages for specifics, but an abstract machine for reliably providing/generating/loading keys via arbitrary helpers is defined alongside the frame-work for doing so via executables. It could be trivially extended for use with, e.g., unix-domain sockets.
I'm not sure about the many localised error strings (and errnos in the wait status handling) in
execute_key_provider()
. I assume someone will have better ones.How Has This Been Tested?
I ran it and non-helper friends into every induced fault I could think of (and a few I couldn't).
Types of changes
Checklist:
Signed-off-by
.