-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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
chrootenv: resolve #32802, #32876, #32877, #32878 #32899
Conversation
if ((env = getenv(names[i]))) { | ||
if (asprintf(ptr++, "%s=%s", names[i], env) < 0) | ||
errorf(EX_OSERR, "asprintf"); | ||
#define LEN(x) sizeof(x) / sizeof(*x) |
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.
nitpick:
- #define LEN(x) sizeof(x) / sizeof(*x)
+ #define LEN(x) (sizeof(x) / sizeof(*x))
char *env_blacklist[] = {}; | ||
|
||
char **env_filter(char *envp[]) { | ||
char **filtered_envp = malloc(sizeof(*envp)); |
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.
missing check if malloc failed.
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.
Thanks!
char *env_blacklist[] = {}; | ||
|
||
char **env_filter(char *envp[]) { | ||
char **filtered_envp = malloc(sizeof(*envp)); |
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.
missing check if malloc failed. Consider to use calloc
, zero initialized memory is usually easier to debug.
bool blacklisted = false; | ||
|
||
for (size_t i = 0; i < LEN(env_blacklist); i++) { | ||
if (!strncmp(*envp, env_blacklist[i], strlen(env_blacklist[i]))) { |
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.
What is the gain of strncmp here? You compute strlen
first based on env_blacklist[i]
.
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.
Because I only need to partially match the string. Example of valid entry in env_blacklist
would be PWD=
, example of *envp
that would be blacklisted is PWD=/home/user
.
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.
The second parameter should be the index of the first '=' in the string indeed.
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.
@dezgeg I don't think I understand. Could you elaborate?
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.
Sorry, that comment was a brain fart. But consider the case where env_blacklist
contains FOO
and *envp
is FOOBAR
.
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.
Yeah, the point was that env_blacklist
elements would always end with an equal sign. But unsetenv
approach is much better. Also I've discovered environ
only today.
char **filtered_envp = malloc(sizeof(*envp)); | ||
size_t n = 0; | ||
|
||
while (*envp != NULL) { |
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.
a for loop incrementing envp would be slightly more readable here.
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.
Agreed!
if (path == NULL) | ||
errorf(EX_OSERR, "malloc"); | ||
|
||
if (strcpy(path, dir) < 0) |
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.
path
is not freed.
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.
It should not be freed, the point is to allocate enough memory to hold combined string to reduce boilerplate. free()
happens at other places.
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.
strcpy() can't fail, btw.
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.
Thanks!
if (strcpy(path, dir) < 0) | ||
errorf(EX_IOERR, "strcpy"); | ||
|
||
if (strcat(path, name) < 0) |
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.
path
is not freed
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.
See above.
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.
Neither can strcat().
|
||
bool isdir(char *path) { | ||
struct stat buf; | ||
stat(path, &buf); |
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.
Should check return value of stat
-> buf could even contain the result of previous values stat calls.
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.
You are right, I'll reimplement this on top of opendir
.
char *host_target = strjoin("host/", prefix_dirent->d_name); | ||
|
||
if (symlink(host_target, prefix_dirent->d_name) < 0) | ||
errorf(EX_IOERR, "symlink"); |
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.
Directory is not closed and memory is not freed in error case.
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 don't understand why this has to be done. I'll read up on it.
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 think this should be handled by exit, not manually. Directories, files and memory are handled by default, and if there's something that is not handled in a larger program, atexit
callback should be used.
@@ -170,7 +239,8 @@ int main(int argc, char *argv[]) { | |||
if (waitpid(cpid, &status, 0) < 0) | |||
errorf(EX_OSERR, "waitpid"); | |||
|
|||
if (nftw(root, nftw_rm, getdtablesize(), FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < 0) | |||
if (nftw(root, nftw_rm, getdtablesize(), FTW_DEPTH | FTW_MOUNT | FTW_PHYS) < | |||
0) |
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.
Write return code to a local variable instead of line wrapping here for readability.
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.
Line wrapping will go away if I rename nftw_rm
.
while (*envp != NULL) { | ||
bool blacklisted = false; | ||
|
||
for (size_t i = 0; i < LEN(env_blacklist); i++) { |
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.
Ok it's correct after all.LEN()
returns something completely wrong here
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 don't think so:
$ cat example.c
#include <stdio.h>
#define LEN(x) sizeof(x) / sizeof(*x)
char *zzz[] = {"a", "b", "c"};
int main() {
printf("%lu\n", LEN(zzz));
return 0;
}
$ nix-shell -p gcc gnumake --run 'make example'
$ ./example
3
@@ -161,7 +230,7 @@ int main(int argc, char *argv[]) { | |||
|
|||
argv++; | |||
|
|||
if (execvpe(*argv, argv, env_build(env_whitelist, LEN(env_whitelist))) < 0) | |||
if (execvpe(*argv, argv, env_filter(environ)) < 0) |
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 think the code for the blacklist approach would be much simpler if you just used execvp()
and just modified the current environment with unsetenv()
.
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 agree, I'm going to do that. Thanks!
errorf(EX_OSERR, "mount"); | ||
} | ||
|
||
char *strjoin(char *dir, char *name) { |
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.
This whole function could just be asprintf(&ret, "%s%s", dir, name)
or in C++: dir + name
. ;)
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 know it could be asprintf
, but it's cleaner this way. Check previous chrootenv version, I've used asprintf
.
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.
How is it cleaner to write 13 lines of code to replace 1 line?
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.
It has better semantics.
There is no template, only concatenation, while asprintf
has its own DSL.
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.
But every C programmer who reads asprintf(&ret, "%s%s", x, y)
immediately knows it's just concatenating two strings.
And in fact you're using this in precisely one place, with a constant string as the other arg. Just
char *host_target = strjoin("host/", prefix_dirent->d_name)
->
char *host_target;
asprintf(&host_target, "host/%s", prefix_dirent->d_name);
to make it even more simpler.
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.
OK, point taken. errorf
uses the same DSL anyway so it seems there is not much use in implementing simpler asprintf
, at least not without dropping errorf
.
return path; | ||
} | ||
|
||
char *bind_blacklist[] = {".", "..", "bin", "etc", "host", "usr"}; |
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.
const char * const bind_blacklist[]
etc.
errorf(EX_OSERR, "asprintf"); | ||
#define LEN(x) sizeof(x) / sizeof(*x) | ||
|
||
char *env_blacklist[] = {}; |
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.
Isn't this variable totally unused?
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.
Ah it's supposed to be a constant... const char * const
then.
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.
It's unused for now.
This variable is supposed to be filled in presumably when @abbradar gets better.
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.
Then I would add a TODO comment.
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.
Will do!
|
||
if (setenv("NIX_CHROOTENV", "1", false) < 0) | ||
errorf(EX_IOERR, "setenv"); | ||
|
||
char tmpl[] = "/tmp/chrootenvXXXXXX"; | ||
char *root = mkdtemp(tmpl); |
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.
root
is not removed in the error case.
* Wrap LEN macro in parantheses * Drop env_filter in favor of stateful environ_blacklist_filter, use execvp instead of execvpe, don't explicitly use environ * Add argument error logging wherever it makes sense * Drop strjoin in favor of asprintf * char* -> const char* where appropriate * Handle stat errors * Print user messages with fputs, not errorf * Abstract away is_str_in (previously bind_blacklisted) * Cleanup temporary directory on error * Some minor syntactic and naming changes Thanks to Jörg Thalheim and Tuomas Tynkkynen for the code review!
c43c660
to
08156be
Compare
@Mic92, @dezgeg: again, thanks for the review. I've fixed most of the problems that you told me about in 08156be (see commit log) except where I explicitly disagree:
|
I plan to merge this tomorrow. |
Motivation for this change
sysctl
command whenkernel.unpriveleged_userns_clone
is available (chrootenv: more helpful error message for Debian users #32876)env_whitelist
withenv_blacklist
(currently empty) (chrootenv: replace environment variable whitelist with a blacklist #32878)Formatted via
clang-format
. Valgrind reports "all heap blocks were freed". Steam still works!Things done
build-use-sandbox
innix.conf
on non-NixOS)nix-shell -p nox --run "nox-review wip"
./result/bin/
)