1
0
Fork 0
mirror of https://gitlab.com/sortix/sortix.git synced 2023-02-13 20:55:38 -05:00

Improve execvpe(3) logic and run shell on ENOEXEC.

This commit is contained in:
Jonas 'Sortie' Termansen 2014-05-12 19:49:30 +02:00
parent f44e46cde5
commit 3577cb81fe
2 changed files with 174 additions and 68 deletions

View file

@ -23,11 +23,13 @@
*******************************************************************************/ *******************************************************************************/
#include <errno.h> #include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h> #include <unistd.h>
// TODO: Move this to some generic environment interface! // TODO: Move this to some generic environment interface.
static const char* LookupEnvironment(const char* name, char* const* envp) static const char* LookupEnvironment(const char* name, char* const* envp)
{ {
size_t equalpos = strcspn(name, "="); size_t equalpos = strcspn(name, "=");
@ -45,21 +47,66 @@ static const char* LookupEnvironment(const char* name, char* const* envp)
return NULL; return NULL;
} }
// TODO: Provide an interface that allows user-space to find out which command static
// would have been executed (according to PATH) had execvpe been called now. int execvpe_attempt(const char* filename,
// This is of value to programs such as which(1), instead of repeating much of const char* original,
// this logic there. char* const* argv,
extern "C" int execvpe(const char* filename, char* const* argv, char* const* envp)
char* const* envp)
{ {
execve(filename, argv, envp);
if ( errno != ENOEXEC )
return -1;
// Prevent attempting to run the shell using itself in an endless loop if it
// happens to be an unknown format or a shell script itself.
if ( !strcmp(original, "sh") )
return errno = ENOEXEC, -1;
int argc = 0;
while ( argv[argc] )
argc++;
if ( INT_MAX - argc < 1 + 1 )
return errno = EOVERFLOW, -1;
int new_argc = 1 + argc;
char** new_argv = (char**) malloc(sizeof(char*) * (new_argc + 1));
if ( !new_argv )
return -1;
new_argv[0] = (char*) "sh";
new_argv[1] = (char*) filename;
for ( int i = 1; i < argc; i++ )
new_argv[1 + i] = argv[i];
new_argv[new_argc] = (char*) NULL;
execvpe(new_argv[0], new_argv, envp);
free(new_argv);
return errno = ENOEXEC, -1;
}
// NOTE: The PATH-searching logic is repeated multiple places. Until this logic
// can be shared somehow, you need to keep this comment in sync as well
// as the logic in these files:
// * libc/unistd/execvpe.cpp
// * utils/which.cpp
extern "C"
int execvpe(const char* filename, char* const* argv, char* const* envp)
{
if ( !filename || !filename[0] )
return errno = ENOENT;
const char* path = LookupEnvironment("PATH", envp); const char* path = LookupEnvironment("PATH", envp);
// TODO: Should there be a default PATH value? bool search_path = !strchr(filename, '/') && path;
if ( strchr(filename, '/') || !path ) bool any_tries = false;
return execve(filename, argv, envp); bool any_eacces = false;
// Search each directory in the PATH variable for a suitable file. // Search each directory in the PATH variable for a suitable file.
size_t filename_len = strlen(filename); while ( search_path && *path )
while ( *path )
{ {
size_t len = strcspn(path, ":"); size_t len = strcspn(path, ":");
if ( !len ) if ( !len )
@ -68,33 +115,61 @@ extern "C" int execvpe(const char* filename, char* const* argv,
// directory. While it does inductively make sense, the common // directory. While it does inductively make sense, the common
// kernel interfaces such as openat doesn't accept it and software // kernel interfaces such as openat doesn't accept it and software
// often just prefix their directories and a colon to PATH without // often just prefix their directories and a colon to PATH without
// regard to whether it's already empty. S // regard to whether it's already empty.
path++; path++;
continue; continue;
} }
// Determine the full path to the file if it is in the directory. any_tries = true;
size_t fullpath_len = len + 1 + filename_len + 1;
char* fullpath = (char*) malloc(fullpath_len * sizeof(char)); // Determine the directory prefix.
if ( !fullpath ) char* dirpath = strndup(path, len);
if ( !dirpath )
return -1; return -1;
stpcpy(stpcpy(stpncpy(fullpath, path, len), "/"), filename);
if ( (path += len)[0] == ':' ) if ( (path += len)[0] == ':' )
path++; path++;
while ( len && dirpath[len - 1] == '/' )
dirpath[--len] = '\0';
// Determine the full path to the file inside the directory.
char* fullpath;
if ( asprintf(&fullpath, "%s/%s", dirpath, filename) < 0 )
return free(dirpath), -1;
execvpe_attempt(fullpath, filename, argv, envp);
execve(fullpath, argv, envp);
free(fullpath); free(fullpath);
free(dirpath);
// TODO: There may be some security concerns here as to whether to // Proceed to the next PATH entry if the file didn't exist.
// continue or abort execution. For instance, if a directory in the
// start of the PATH has permissions set up too restrictively, then
// it would never look in the later directories (and you can't execute
// anything without absolute paths). And other situations.
if ( errno == EACCES )
return -1;
if ( errno == ENOENT ) if ( errno == ENOENT )
continue; continue;
// Ignore errors related to path resolution where the cause is a bad
// entry in the PATH as opposed to security issues.
if ( errno == ELOOP ||
errno == EISDIR ||
errno == ENAMETOOLONG ||
errno == ENOTDIR )
continue;
// Remember permission denied errors and report that errno value if the
// entire PATH search fails rather than the error of the last attempt.
if ( errno == EACCES )
{
any_eacces = true;
continue;
}
// Any other errors are treated as fatal and we stop the search.
break;
} }
if ( !any_tries )
execvpe_attempt(filename, filename, argv, envp);
if ( any_eacces )
errno = EACCES;
return -1; return -1;
} }

View file

@ -1,6 +1,6 @@
/******************************************************************************* /*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2012. Copyright(C) Jonas 'Sortie' Termansen 2012, 2014.
This program is free software: you can redistribute it and/or modify it This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free under the terms of the GNU General Public License as published by the Free
@ -35,54 +35,90 @@
#define VERSIONSTR "unknown version" #define VERSIONSTR "unknown version"
#endif #endif
bool Which(const char* cmd, const char* path, bool all) // NOTE: The PATH-searching logic is repeated multiple places. Until this logic
{ // can be shared somehow, you need to keep this comment in sync as well
if ( strchr(cmd, '/') ) // as the logic in these files:
{ // * libc/unistd/execvpe.cpp
struct stat st; // * utils/which.cpp
if ( stat(path, &st) ) // NOTO: See comments in execvpe() for algorithmic commentary.
{
printf("%s\n", cmd);
return true;
}
return false;
}
// Sortix doesn't support that the empty string means current directory. bool Which(const char* filename, const char* path, bool all)
{
bool found = false; bool found = false;
char* dirname = NULL;
while ( *path ) bool search_path = !strchr(filename, '/') && path;
bool any_tries = false;
bool any_eacces = false;
while ( search_path && *path )
{ {
if ( dirname ) { free(dirname); dirname = NULL; }
size_t len = strcspn(path, ":"); size_t len = strcspn(path, ":");
if ( !len ) { path++; continue; } if ( !len )
dirname = (char*) malloc((len+1) * sizeof(char)); {
if ( !dirname ) path++;
error(1, errno, "malloc"); continue;
memcpy(dirname, path, len * sizeof(char)); }
dirname[len] = '\0';
any_tries = true;
char* dirpath = strndup(path, len);
if ( !dirpath )
return -1;
if ( (path += len)[0] == ':' ) if ( (path += len)[0] == ':' )
path++; path++;
int dirfd = open(dirname, O_RDONLY | O_DIRECTORY); while ( len && dirpath[len - 1] == '/' )
if ( dirfd < 0 ) dirpath[--len] = '\0';
char* fullpath;
if ( asprintf(&fullpath, "%s/%s", dirpath, filename) < 0 )
return free(dirpath), -1;
if ( access(fullpath, X_OK) == 0 )
{ {
if ( errno == EACCES ) found = true;
error(1, errno, "%s", dirname); printf("%s\n", fullpath);
// TODO: May be a security concern to continue; free(fullpath);
if ( errno == ENOENT ) free(dirpath);
if ( all )
continue; continue;
break;
}
free(fullpath);
free(dirpath);
if ( errno == ENOENT )
continue;
if ( errno == ELOOP ||
errno == EISDIR ||
errno == ENAMETOOLONG ||
errno == ENOTDIR )
continue;
if ( errno == EACCES )
{
any_eacces = true;
continue; continue;
} }
struct stat st;
int ret = fstatat(dirfd, cmd, &st, 0); if ( all )
if ( ret != 0 )
continue; continue;
printf("%s/%s\n", dirname, cmd);
found = true; break;
if ( !all )
break;
} }
free(dirname);
if ( !any_tries )
{
if ( access(filename, X_OK) == 0 )
{
found = true;
printf("%s\n", filename);
}
}
(void) any_eacces;
return found; return found;
} }
@ -144,11 +180,6 @@ int main(int argc, char* argv[])
} }
const char* path = getenv("PATH"); const char* path = getenv("PATH");
if ( !path )
{
fprintf(stderr, "%s: PATH variable is not set\n", argv0);
exit(1);
}
bool success = true; bool success = true;
for ( int i = 1; i < argc; i++ ) for ( int i = 1; i < argc; i++ )