Enable SFTP chroot support (#308)

- Added chroot implementation that simply stores the path in internal state and sets an environment variable
- Spawned processes pickup chroot from environment variable
- Core change in realpath and resolved_path_utf16 now take into account chroot path.
- Unit tests
- Other miscellaneous changes to account for chroot enabled logic in core code

PowerShell/Win32-OpenSSH#190
PowerShell/Win32-OpenSSH#292
This commit is contained in:
Manoj Ampalam 2018-05-11 14:45:20 -07:00 committed by GitHub
parent 936b89ac0d
commit 7b28a316eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 484 additions and 143 deletions

View File

@ -96,6 +96,7 @@ errno_from_Win32Error(int win32_error)
return EEXIST;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_NAME:
return ENOENT;
default:
return win32_error;
@ -421,14 +422,36 @@ cleanup:
return ret;
}
/* returns 1 if true, 0 otherwise */
int
file_in_chroot_jail(HANDLE handle, const char* path_utf8) {
/* ensure final path is within chroot */
wchar_t path_buf[MAX_PATH], *final_path;
if (GetFinalPathNameByHandleW(handle, path_buf, MAX_PATH, 0) == 0) {
debug3("failed to get final path of file:%s error:%d", path_utf8, GetLastError());
return 0;
}
final_path = path_buf + 4;
to_wlower_case(final_path);
if ((wcslen(final_path) < wcslen(chroot_pathw)) ||
memcmp(final_path, chroot_pathw, 2 * wcslen(chroot_pathw)) != 0 ||
final_path[wcslen(chroot_pathw)] != '\\') {
debug3("access denied due to attempt to escape chroot jail");
return 0;
}
return 1;
}
/* open() implementation. Uses CreateFile to open file, console, device, etc */
struct w32_io*
fileio_open(const char *path_utf8, int flags, mode_t mode)
{
struct w32_io* pio = NULL;
struct createFile_flags cf_flags;
HANDLE handle;
HANDLE handle = INVALID_HANDLE_VALUE;
wchar_t *path_utf16 = NULL;
int nonfs_dev = 0; /* opening a non file system device */
debug4("open - pathname:%s, flags:%d, mode:%d", path_utf8, flags, mode);
/* check input params*/
@ -439,14 +462,15 @@ fileio_open(const char *path_utf8, int flags, mode_t mode)
}
/* if opening null device, point to Windows equivalent */
if (strncmp(path_utf8, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0)
path_utf8 = NULL_DEVICE_WIN;
if ((path_utf16 = resolved_path_utf16(path_utf8)) == NULL) {
errno = ENOMEM;
debug3("utf8_to_utf16 failed for file:%s error:%d", path_utf8, GetLastError());
return NULL;
if (strncmp(path_utf8, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0) {
nonfs_dev = 1;
path_utf16 = utf8_to_utf16(NULL_DEVICE_WIN);
}
else
path_utf16 = resolved_path_utf16(path_utf8);
if (path_utf16 == NULL)
return NULL;
if (createFile_flags_setup(flags, mode, &cf_flags) == -1) {
debug3("createFile_flags_setup() failed.");
@ -463,6 +487,10 @@ fileio_open(const char *path_utf8, int flags, mode_t mode)
goto cleanup;
}
if (chroot_pathw && !nonfs_dev && !file_in_chroot_jail(handle, path_utf8)) {
errno = EACCES;
goto cleanup;
}
pio = (struct w32_io*)malloc(sizeof(struct w32_io));
if (pio == NULL) {
@ -478,11 +506,16 @@ fileio_open(const char *path_utf8, int flags, mode_t mode)
pio->fd_status_flags = O_NONBLOCK;
pio->handle = handle;
handle = INVALID_HANDLE_VALUE;
cleanup:
if ((&cf_flags.securityAttributes != NULL) && (&cf_flags.securityAttributes.lpSecurityDescriptor != NULL))
LocalFree(cf_flags.securityAttributes.lpSecurityDescriptor);
if(path_utf16)
free(path_utf16);
if (handle != INVALID_HANDLE_VALUE)
CloseHandle(handle);
return pio;
}
@ -774,11 +807,8 @@ fileio_stat_or_lstat_internal(const char *path, struct _stat64 *buf, int do_lsta
return 0;
}
if ((wpath = resolved_path_utf16(path)) == NULL) {
errno = ENOMEM;
debug3("utf8_to_utf16 failed for file:%s error:%d", path, GetLastError());
if ((wpath = resolved_path_utf16(path)) == NULL)
return -1;
}
/* get the file attributes (or symlink attributes if symlink) */
if (GetFileAttributesExW(wpath, GetFileExInfoStandard, &attributes) == FALSE) {
@ -1039,10 +1069,8 @@ fileio_readlink(const char *path, char *buf, size_t bufsiz)
goto cleanup;
}
if ((wpath = resolved_path_utf16(path)) == NULL) {
errno = ENOMEM;
if ((wpath = resolved_path_utf16(path)) == NULL)
goto cleanup;
}
/* obtain a handle to send to deviceioctl */
handle = CreateFileW(wpath, 0, 0, NULL, OPEN_EXISTING,
@ -1123,18 +1151,21 @@ cleanup:
int
fileio_symlink(const char *target, const char *linkpath)
{
DWORD ret = -1;
if (target == NULL || linkpath == NULL) {
errno = EFAULT;
return -1;
}
DWORD ret = 0;
wchar_t *target_utf16 = resolved_path_utf16(target);
wchar_t *linkpath_utf16 = resolved_path_utf16(linkpath);
wchar_t *resolved_utf16 = _wcsdup(target_utf16);
if (target_utf16 == NULL || linkpath_utf16 == NULL || resolved_utf16 == NULL) {
if (target_utf16 == NULL || linkpath_utf16 == NULL)
goto cleanup;
if (resolved_utf16 == NULL) {
errno = ENOMEM;
ret = -1;
goto cleanup;
}
@ -1151,7 +1182,6 @@ fileio_symlink(const char *target, const char *linkpath)
resolved_utf16 = malloc(resolved_len * sizeof(wchar_t));
if (resolved_utf16 == NULL) {
errno = ENOMEM;
ret = -1;
goto cleanup;
}
@ -1171,7 +1201,6 @@ fileio_symlink(const char *target, const char *linkpath)
WIN32_FILE_ATTRIBUTE_DATA attributes = { 0 };
if (GetFileAttributesExW(resolved_utf16, GetFileExInfoStandard, &attributes) == FALSE) {
errno = errno_from_Win32LastError();
ret = -1;
goto cleanup;
}
@ -1187,11 +1216,11 @@ fileio_symlink(const char *target, const char *linkpath)
if (CreateSymbolicLinkW(linkpath_utf16, target_utf16, create_flags) == 0) {
if (CreateSymbolicLinkW(linkpath_utf16, target_utf16, create_flags | 0x2) == 0) {
errno = errno_from_Win32LastError();
ret = -1;
goto cleanup;
}
}
ret = 0;
cleanup:
if (target_utf16)
@ -1206,28 +1235,25 @@ cleanup:
int
fileio_link(const char *oldpath, const char *newpath)
{
DWORD ret = -1;
if (oldpath == NULL || newpath == NULL) {
errno = EFAULT;
return -1;
}
DWORD ret = 0;
wchar_t *oldpath_utf16 = resolved_path_utf16(oldpath);
wchar_t *newpath_utf16 = resolved_path_utf16(newpath);
if (oldpath_utf16 == NULL || newpath_utf16 == NULL) {
errno = ENOMEM;
ret = -1;
if (oldpath_utf16 == NULL || newpath_utf16 == NULL)
goto cleanup;
}
if (CreateHardLinkW(newpath_utf16, oldpath_utf16, NULL) == 0) {
errno = errno_from_Win32LastError();
ret = -1;
goto cleanup;
}
ret = 0;
cleanup:
if (oldpath_utf16)

View File

@ -19,8 +19,8 @@ struct dirent {
typedef struct DIR_ DIR;
DIR * opendir(const char *name);
int closedir(DIR *dirp);
struct dirent *readdir(void *avp);
DIR * opendir(const char*);
int closedir(DIR*);
struct dirent *readdir(void*);
#endif

View File

@ -132,6 +132,12 @@ char* _sys_errlist_ext[] = {
"Operation would block" /* EWOULDBLOCK 140 */
};
/* chroot state */
char* chroot_path = NULL;
int chroot_path_len = 0;
/* UTF-16 version of the above */
wchar_t* chroot_pathw = NULL;
int
usleep(unsigned int useconds)
{
@ -247,6 +253,7 @@ w32_fopen_utf8(const char *input_path, const char *mode)
char first3_bytes[3];
int status = 1;
errno_t r = 0;
int nonfs_dev = 0; /* opening a non file system device */
if (mode == NULL || mode[1] != '\0') {
errno = ENOTSUP;
@ -260,21 +267,32 @@ w32_fopen_utf8(const char *input_path, const char *mode)
}
/* if opening null device, point to Windows equivalent */
if (strncmp(input_path, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0)
input_path = NULL_DEVICE_WIN;
wpath = resolved_path_utf16(input_path);
if (strncmp(input_path, NULL_DEVICE, sizeof(NULL_DEVICE)) == 0) {
nonfs_dev = 1;
wpath = utf8_to_utf16(NULL_DEVICE_WIN);
}
else
wpath = resolved_path_utf16(input_path);
wmode = utf8_to_utf16(mode);
if (wpath == NULL || wmode == NULL)
{
errno = ENOMEM;
goto cleanup;
}
if ((_wfopen_s(&f, wpath, wmode) != 0) || (f == NULL)) {
debug3("Failed to open file:%s error:%d", input_path, errno);
goto cleanup;
}
}
if (chroot_pathw && !nonfs_dev) {
/* ensure final path is within chroot */
HANDLE h = (HANDLE)_get_osfhandle(_fileno(f));
if (!file_in_chroot_jail(h, input_path)) {
fclose(f);
f = NULL;
errno = EACCES;
goto cleanup;
}
}
/* BOM adjustments for file streams*/
if (mode[0] == 'w' && fseek(f, 0, SEEK_SET) != EBADF) {
@ -523,10 +541,9 @@ w32_chmod(const char *pathname, mode_t mode)
{
int ret;
wchar_t *resolvedPathName_utf16 = resolved_path_utf16(pathname);
if (resolvedPathName_utf16 == NULL) {
errno = ENOMEM;
if (resolvedPathName_utf16 == NULL)
return -1;
}
ret = _wchmod(resolvedPathName_utf16, mode);
free(resolvedPathName_utf16);
return ret;
@ -651,10 +668,9 @@ w32_utimes(const char *filename, struct timeval *tvp)
int ret;
FILETIME acttime, modtime;
wchar_t *resolvedPathName_utf16 = resolved_path_utf16(filename);
if (resolvedPathName_utf16 == NULL) {
errno = ENOMEM;
if (resolvedPathName_utf16 == NULL)
return -1;
}
memset(&acttime, 0, sizeof(FILETIME));
memset(&modtime, 0, sizeof(FILETIME));
@ -688,11 +704,9 @@ w32_rename(const char *old_name, const char *new_name)
wchar_t *resolvedOldPathName_utf16 = resolved_path_utf16(old_name);
wchar_t *resolvedNewPathName_utf16 = resolved_path_utf16(new_name);
if (NULL == resolvedOldPathName_utf16 || NULL == resolvedNewPathName_utf16) {
errno = ENOMEM;
if (NULL == resolvedOldPathName_utf16 || NULL == resolvedNewPathName_utf16)
return -1;
}
/*
* To be consistent with POSIX rename(),
* 1) if the new_name is file, then delete it so that _wrename will succeed.
@ -725,10 +739,8 @@ int
w32_unlink(const char *path)
{
wchar_t *resolvedPathName_utf16 = resolved_path_utf16(path);
if (NULL == resolvedPathName_utf16) {
errno = ENOMEM;
if (NULL == resolvedPathName_utf16)
return -1;
}
int returnStatus = _wunlink(resolvedPathName_utf16);
free(resolvedPathName_utf16);
@ -740,10 +752,8 @@ int
w32_rmdir(const char *path)
{
wchar_t *resolvedPathName_utf16 = resolved_path_utf16(path);
if (NULL == resolvedPathName_utf16) {
errno = ENOMEM;
if (NULL == resolvedPathName_utf16)
return -1;
}
int returnStatus = _wrmdir(resolvedPathName_utf16);
free(resolvedPathName_utf16);
@ -754,11 +764,9 @@ w32_rmdir(const char *path)
int
w32_chdir(const char *dirname_utf8)
{
wchar_t *dirname_utf16 = utf8_to_utf16(dirname_utf8);
if (dirname_utf16 == NULL) {
errno = ENOMEM;
wchar_t *dirname_utf16 = resolved_path_utf16(dirname_utf8);
if (dirname_utf16 == NULL)
return -1;
}
int returnStatus = _wchdir(dirname_utf16);
free(dirname_utf16);
@ -792,6 +800,30 @@ w32_getcwd(char *buffer, int maxlen)
return NULL;
free(putf8);
to_lower_case(buffer);
if (chroot_path) {
/* ensure we are within chroot jail */
char c = buffer[chroot_path_len];
if ( strlen(buffer) < chroot_path_len ||
memcmp(chroot_path, buffer, chroot_path_len) != 0 ||
(c != '\0' && c!= '\\') ) {
errno = EOTHER;
error("cwb is not currently within chroot");
return NULL;
}
/* is cwd chroot ?*/
if (c == '\0') {
buffer[0] = '\\';
buffer[1] = '\0';
}
else {
char *tail = buffer + chroot_path_len;
memmove_s(buffer, maxlen, tail, strlen(tail) + 1);
}
}
return buffer;
}
@ -800,10 +832,9 @@ w32_mkdir(const char *path_utf8, unsigned short mode)
{
int curmask;
wchar_t *path_utf16 = resolved_path_utf16(path_utf8);
if (path_utf16 == NULL) {
errno = ENOMEM;
if (path_utf16 == NULL)
return -1;
}
int returnStatus = _wmkdir(path_utf16);
if (returnStatus < 0) {
free(path_utf16);
@ -878,25 +909,55 @@ convertToForwardslash(char *str)
char *
realpath(const char *path, char resolved[PATH_MAX])
{
errno_t r = 0;
if (!path || !resolved) return NULL;
char tempPath[PATH_MAX];
size_t path_len = strlen(path);
resolved[0] = '\0';
if (path_len > PATH_MAX - 1) {
errno = EINVAL;
return NULL;
}
if ((path_len >= 2) && (path[0] == '/') && path[1] && (path[2] == ':')) {
if((r = strncpy_s(resolved, PATH_MAX, path + 1, path_len)) != 0 ) /* skip the first '/' */ {
debug3("memcpy_s failed with error: %d.", r);
/* resolve root directory to the same */
if (path_len == 1 && (path[0] == '/' || path[0] == '\\')) {
resolved[0] = '/';
resolved[1] = '\0';
return resolved;
}
/* resolve this common case scenario to root */
/* "cd .." from within a drive root */
if (path_len == 6 && !chroot_path) {
char *tmplate = "/x:/..";
strcat(resolved, path);
resolved[1] = 'x';
if (strcmp(tmplate, resolved) == 0) {
resolved[0] = '/';
resolved[1] = '\0';
return resolved;
}
}
if (chroot_path) {
resolved[0] = '\0';
strcat(resolved, chroot_path);
/* if path is relative, add cwd within chroot */
if (path[0] != '/' && path[0] != '\\') {
w32_getcwd(resolved + chroot_path_len, PATH_MAX - chroot_path_len);
strcat(resolved, "/");
}
strcat(resolved, path);
}
else if ((path_len >= 2) && (path[0] == '/') && path[1] && (path[2] == ':')) {
if((errno = strncpy_s(resolved, PATH_MAX, path + 1, path_len)) != 0 ) /* skip the first '/' */ {
debug3("memcpy_s failed with error: %d.", errno);
return NULL;
}
}
else if(( r = strncpy_s(resolved, PATH_MAX, path, path_len + 1)) != 0) {
debug3("memcpy_s failed with error: %d.", r);
else if(( errno = strncpy_s(resolved, PATH_MAX, path, path_len + 1)) != 0) {
debug3("memcpy_s failed with error: %d.", errno);
return NULL;
}
@ -905,27 +966,78 @@ realpath(const char *path, char resolved[PATH_MAX])
resolved[3] = '\0';
}
if (_fullpath(tempPath, resolved, PATH_MAX) == NULL)
return NULL;
convertToForwardslash(tempPath);
resolved[0] = '/'; /* will be our first slash in /x:/users/test1 format */
if ((r = strncpy_s(resolved+1, PATH_MAX - 1, tempPath, sizeof(tempPath) - 1)) != 0) {
debug3("memcpy_s failed with error: %d.", r);
if (_fullpath(tempPath, resolved, PATH_MAX) == NULL) {
errno = EINVAL;
return NULL;
}
return resolved;
if (chroot_path) {
if (strlen(tempPath) < strlen(chroot_path)) {
errno = EACCES;
return NULL;
}
if (memcmp(chroot_path, tempPath, strlen(chroot_path)) != 0) {
errno = EACCES;
return NULL;
}
resolved[0] = '\0';
if (strlen(tempPath) == strlen(chroot_path))
/* realpath is the same as chroot_path */
strcat(resolved, "\\");
else
strcat(resolved, tempPath + strlen(chroot_path));
if (resolved[0] != '\\') {
errno = EACCES;
return NULL;
}
convertToForwardslash(resolved);
return resolved;
}
else {
convertToForwardslash(tempPath);
resolved[0] = '/'; /* will be our first slash in /x:/users/test1 format */
if ((errno = strncpy_s(resolved + 1, PATH_MAX - 1, tempPath, sizeof(tempPath) - 1)) != 0) {
debug3("memcpy_s failed with error: %d.", errno);
return NULL;
}
return resolved;
}
}
/* on error returns NULL and sets errno */
wchar_t*
resolved_path_utf16(const char *input_path)
{
if (!input_path) return NULL;
wchar_t *resolved_path = NULL;
wchar_t * resolved_path = utf8_to_utf16(input_path);
if (resolved_path == NULL)
if (!input_path) {
errno = EINVAL;
return NULL;
}
if (chroot_path) {
char actual_path[MAX_PATH], jail_path[MAX_PATH];
if (realpath(input_path, jail_path) == NULL)
return NULL;
actual_path[0] = '\0';
strcat_s(actual_path, MAX_PATH, chroot_path);
strcat_s(actual_path, MAX_PATH, jail_path);
resolved_path = utf8_to_utf16(actual_path);
}
else
resolved_path = utf8_to_utf16(input_path);
if (resolved_path == NULL) {
errno = ENOMEM;
return NULL;
}
int resolved_len = (int) wcslen(resolved_path);
const int variable_len = (int) wcslen(PROGRAM_DATAW);
@ -941,8 +1053,9 @@ resolved_path_utf16(const char *input_path)
wchar_t * resolved_path_new = realloc(resolved_path,
(resolved_len + changed_req + 1) * sizeof(wchar_t));
if (resolved_path == NULL) {
debug3("%s: memory allocation failed.", __FUNCTION__);
free(resolved_path);
debug3("%s: memory allocation failed.", __FUNCTION__);
free(resolved_path);
errno = ENOMEM;
return NULL;
}
else resolved_path = resolved_path_new;
@ -979,8 +1092,11 @@ statvfs(const char *path, struct statvfs *buf)
DWORD totalClusters;
wchar_t* path_utf16 = resolved_path_utf16(path);
if (path_utf16 && (GetDiskFreeSpaceW(path_utf16, &sectorsPerCluster, &bytesPerSector,
&freeClusters, &totalClusters) == TRUE)) {
if (path_utf16 == NULL)
return -1;
if (GetDiskFreeSpaceW(path_utf16, &sectorsPerCluster, &bytesPerSector,
&freeClusters, &totalClusters)) {
debug5("path : [%s]", path);
debug5("sectorsPerCluster : [%lu]", sectorsPerCluster);
debug5("bytesPerSector : [%lu]", bytesPerSector);
@ -1004,7 +1120,7 @@ statvfs(const char *path, struct statvfs *buf)
return 0;
} else {
debug5("ERROR: Cannot get free space for [%s]. Error code is : %d.\n", path, GetLastError());
errno = errno_from_Win32LastError();
free(path_utf16);
return -1;
}
@ -1271,6 +1387,13 @@ to_lower_case(char *s)
*s = tolower((u_char)*s);
}
void
to_wlower_case(wchar_t *s)
{
for (; *s; s++)
*s = towlower(*s);
}
static int
get_final_mode(int allow_mode, int deny_mode)
{
@ -1471,3 +1594,46 @@ localtime_r(const time_t *timep, struct tm *result)
memcpy(result, t, sizeof(struct tm));
return t;
}
int
chroot(const char *path)
{
char cwd[MAX_PATH];
if (strcmp(path, ".") == 0) {
if (w32_getcwd(cwd, MAX_PATH) == NULL)
return -1;
path = (const char *)cwd;
} else if (*(path + 1) != ':') {
errno = ENOTSUP;
error("chroot only supports absolute paths");
return -1;
} else {
/* TODO - ensure path exists and is a directory */
}
if ((chroot_path = _strdup(path)) == NULL) {
errno = ENOMEM;
return -1;
}
to_lower_case(chroot_path);
convertToBackslash(chroot_path);
/* strip trailing \ */
if (chroot_path[strlen(chroot_path) - 1] == '\\')
chroot_path[strlen(chroot_path) - 1] = '\0';
chroot_path_len = strlen(chroot_path);
if ((chroot_pathw = utf8_to_utf16(chroot_path)) == NULL) {
errno = ENOMEM;
return -1;
}
/* TODO - set the env variable just in time in a posix_spawn_chroot like API */
#define POSIX_CHROOTW L"c28fc6f98a2c44abbbd89d6a3037d0d9_POSIX_CHROOT"
_wputenv_s(POSIX_CHROOTW, chroot_pathw);
return 0;
}

View File

@ -27,6 +27,10 @@
static char *machine_domain_name;
extern char* chroot_path;
extern int chroot_path_len;
extern wchar_t* chroot_pathw;
/* removes first '/' for Windows paths that are unix styled. Ex: /c:/ab.cd */
wchar_t * resolved_path_utf16(const char *);
void w32posix_initialize();
@ -41,8 +45,10 @@ void file_time_to_unix_time(const LPFILETIME, time_t *);
int file_attr_to_st_mode(wchar_t * path, DWORD attributes);
void invalid_parameter_handler(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t);
void to_lower_case(char *s);
void to_wlower_case(wchar_t *s);
wchar_t* get_program_data_path();
HANDLE get_user_token(char* user, int impersonation);
int load_user_profile(HANDLE user_token, char* user);
int create_directory_withsddl(wchar_t *path, wchar_t *sddl);
int is_absolute_path(const char *);
int is_absolute_path(const char *);
int file_in_chroot_jail(HANDLE, const char*);

View File

@ -90,19 +90,14 @@ innetgr(const char *netgroup, const char *host, const char *user, const char *do
return -1;
}
/* sshd.c */
int
chroot(const char *path)
initgroups(const char *user, gid_t group)
{
return 0;
}
int
initgroups(const char *user, gid_t group)
{
return -1;
}
/* sshd.c */
int
setgroups(gid_t group, char* name)
{

View File

@ -260,7 +260,7 @@ user_from_uid(uid_t uid, int nouser)
uid_t
getuid(void)
{
return 0;
return 1;
}
gid_t
@ -272,7 +272,7 @@ getgid(void)
uid_t
geteuid(void)
{
return 0;
return 1;
}
gid_t

View File

@ -318,6 +318,11 @@ STARTUPINFO inputSi;
goto cleanup; \
} while(0)
void
debug3(const char *s, ...) {
return;
}
int
ConSRWidth()
{

View File

@ -30,7 +30,9 @@
#include <Windows.h>
#include "inc\utf.h"
#include "Debug.h"
/*on error returns NULL and sets errno*/
wchar_t *
utf8_to_utf16(const char *utf8)
{
@ -38,8 +40,11 @@ utf8_to_utf16(const char *utf8)
wchar_t* utf16 = NULL;
if ((needed = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0)) == 0 ||
(utf16 = malloc(needed * sizeof(wchar_t))) == NULL ||
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, needed) == 0)
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, utf16, needed) == 0) {
debug3("failed to convert utf8 payload:%s error:%d", utf8, GetLastError());
errno = ENOMEM;
return NULL;
}
return utf16;
}

View File

@ -71,7 +71,6 @@ check_secure_file_permission(const char *input_path, struct passwd * pw)
if ((path_utf16 = resolved_path_utf16(input_path)) == NULL) {
ret = -1;
errno = ENOMEM;
goto cleanup;
}

View File

@ -67,7 +67,8 @@ HANDLE main_thread;
void fd_table_set(struct w32_io* pio, int index);
void fd_decode_state(char*);
#define POSIX_STATE_ENV "c28fc6f98a2c44abbbd89d6a3037d0d9_POSIX_STATE"
#define POSIX_FD_STATE "c28fc6f98a2c44abbbd89d6a3037d0d9_POSIX_FD_STATE"
#define POSIX_CHROOTW L"c28fc6f98a2c44abbbd89d6a3037d0d9_POSIX_CHROOT"
/* __progname */
char* __progname = "";
@ -76,7 +77,6 @@ char* __progname = "";
static int
fd_table_initialize()
{
char *posix_state;
struct w32_io *pio;
HANDLE wh;
/* table entries representing std in, out and error*/
@ -101,17 +101,33 @@ fd_table_initialize()
}
}
_dupenv_s(&posix_state, NULL, POSIX_STATE_ENV);
/*TODO - validate parent process - to accomodate these scenarios -
* A posix parent process launches a regular process that inturn launches a posix child process
* In this case the posix child process may misinterpret POSIX_STATE_ENV set by grand parent
*/
if (NULL != posix_state) {
fd_decode_state(posix_state);
free(posix_state);
_putenv_s(POSIX_STATE_ENV, "");
/* decode fd state if any */
{
char *posix_fd_state;
_dupenv_s(&posix_fd_state, NULL, POSIX_FD_STATE);
/*TODO - validate parent process - to accomodate these scenarios -
* A posix parent process launches a regular process that inturn launches a posix child process
* In this case the posix child process may misinterpret POSIX_FD_STATE set by grand parent
*/
if (NULL != posix_fd_state) {
fd_decode_state(posix_fd_state);
free(posix_fd_state);
_putenv_s(POSIX_FD_STATE, "");
}
}
/* decode chroot if any */
{
_wdupenv_s(&chroot_pathw, NULL, POSIX_CHROOTW);
if (chroot_pathw != NULL) {
if ((chroot_path = utf16_to_utf8(chroot_pathw)) == NULL)
return -1;
chroot_path_len = strlen(chroot_path);
}
}
return 0;
}
@ -1235,7 +1251,7 @@ posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actio
if ((fd_info = fd_encode_state(file_actions, aux_handles)) == NULL)
goto cleanup;
if (_putenv_s(POSIX_STATE_ENV, fd_info) != 0)
if (_putenv_s(POSIX_FD_STATE, fd_info) != 0)
goto cleanup;
i = spawn_child_internal(argv[0], argv + 1, stdio_handles[STDIN_FILENO], stdio_handles[STDOUT_FILENO], stdio_handles[STDERR_FILENO], sc_flags, user_token);
if (i == -1)
@ -1244,7 +1260,7 @@ posix_spawn_internal(pid_t *pidp, const char *path, const posix_spawn_file_actio
*pidp = i;
ret = 0;
cleanup:
_putenv_s(POSIX_STATE_ENV, "");
_putenv_s(POSIX_FD_STATE, "");
for (i = 0; i <= STDERR_FILENO; i++) {
if (stdio_handles[i] != NULL) {
if (fd_table.w32_ios[file_actions->stdio_redirect[i]]->type == SOCK_FD)

View File

@ -47,10 +47,10 @@ struct DIR_ {
#define ATTR_ROOTDIR UINT_MAX
/* Enumerate all devices which have drive name.
Return a DIR stream on the root directory, or NULL if it could not be enumerated. */
/* Enumerate all devices which have drive name.
Return a DIR stream on the root directory, or NULL if it could not be enumerated. */
DIR *
openrootdir(const char *name)
openrootdir()
{
int hr = 0;
DWORD dw;
@ -104,14 +104,12 @@ opendir(const char *name)
wchar_t* wname = NULL;
size_t len;
/* Detect root dir */
if (name && strcmp(name, "/") == 0)
return openrootdir(name);
if ((wname = resolved_path_utf16(name)) == NULL) {
errno = ENOMEM;
if ((wname = resolved_path_utf16(name)) == NULL)
return NULL;
}
/* Detect root dir */
if (wcscmp(wname, L"/") == 0)
return openrootdir();
convertToBackslashW(wname);
len = wcslen(wname);

View File

@ -82,7 +82,7 @@ platform_post_fork_child(void)
int
platform_privileged_uidswap(void)
{
#ifdef HAVE_CYGWIN
#if defined(HAVE_CYGWIN) || defined(WINDOWS)
/* uid 0 is not special on Cygwin so always try */
return 1;
#else

View File

@ -1,4 +1,4 @@
#include "includes.h"
#include "includes.h"
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <misc_internal.h>
@ -68,6 +68,7 @@ test_sanitizedpath()
ASSERT_PTR_NE(win32prgdir_utf8, NULL);
ASSERT_PTR_EQ(resolved_path_utf16(NULL), NULL);
ASSERT_INT_EQ(errno, EINVAL);
wchar_t *win32prgdir = utf8_to_utf16(win32prgdir_utf8);
wchar_t *ret = resolved_path_utf16(win32prgdir_utf8);
@ -187,9 +188,129 @@ void test_realpath()
ret = realpath("/c:", resolved_path);
ASSERT_STRING_EQ(ret, expectedOutput2);
ASSERT_PTR_NE(ret = realpath("/c:/..", resolved_path), NULL);
ASSERT_STRING_EQ(ret, "/");
ASSERT_PTR_NE(ret = realpath("/", resolved_path), NULL);
ASSERT_STRING_EQ(ret, "/");
ASSERT_PTR_NE(ret = realpath("\\", resolved_path), NULL);
ASSERT_STRING_EQ(ret, "/");
TEST_DONE();
}
void
test_chroot()
{
int fd;
FILE *f;
char path[MAX_PATH], test_root[MAX_PATH];
/* test directory setup */
_wsystem(L"RD /S /Q chroot-testdir >NUL 2>&1");
CreateDirectoryW(L"chroot-testdir", NULL);
CreateDirectoryW(L"chroot-testdir\\world", NULL);
_wsystem(L"echo in-world > chroot-testdir\\world\\w.Txt");
CreateDirectoryW(L"chroot-testdir\\jail", NULL);
CreateDirectoryW(L"chroot-testdir\\jail\\d1", NULL);
_wsystem(L"echo in-jail > chroot-testdir\\jail\\d1\\j.Txt");
/* create links to world within jail */
_wsystem(L"mklink /D chroot-testdir\\jail\\world-sl ..\\world");
_wsystem(L"mklink /J chroot-testdir\\jail\\world-jn chroot-testdir\\world");
TEST_START("chroot on invalid path");
ASSERT_INT_EQ(chroot("blah"), -1);
ASSERT_INT_EQ(chroot("\\c:\\blah"), -1);
ASSERT_INT_EQ(chroot("/c:/blah"), -1);
TEST_DONE();
TEST_START("access world before chroot");
ASSERT_INT_NE(fd = open("chroot-testdir\\jail\\world-jn\\w.Txt", 0), -1);
close(fd);
ASSERT_PTR_NE(f = fopen("chroot-testdir\\jail\\world-jn\\w.Txt", "r"), NULL);
fclose(f);
TEST_DONE();
TEST_START("real chroot now");
getcwd(path, MAX_PATH);
getcwd(test_root, MAX_PATH);
strcat(path, "\\chroot-testdir\\jail");
ASSERT_INT_EQ(chdir(path), 0);
ASSERT_INT_EQ(chroot(path), 0);
TEST_DONE();
TEST_START("chdir; getcwd and realpath");
ASSERT_PTR_NE(getcwd(path, MAX_PATH), NULL);
ASSERT_STRING_EQ(path, "\\");
ASSERT_INT_NE(chdir(test_root), 0);
ASSERT_INT_EQ(chdir("d1"), 0);
ASSERT_PTR_NE(realpath("..", path), NULL);
ASSERT_STRING_EQ(path, "/");
ASSERT_PTR_NE(getcwd(path, MAX_PATH), NULL);
ASSERT_STRING_EQ(path, "\\d1");
ASSERT_PTR_NE(realpath(".", path), NULL);
ASSERT_STRING_EQ(path, "/d1");
ASSERT_PTR_EQ(realpath("..\\..\\", path), NULL);
ASSERT_INT_EQ(errno, EACCES);
TEST_DONE();
TEST_START("file io within jail");
ASSERT_INT_NE(fd = open("\\d1\\j.txt", 0), -1);
close(fd);
ASSERT_INT_NE(fd = open("\\d1/j.txt", 0), -1);
close(fd);
ASSERT_INT_NE(fd = open("/d1/j.txt", 0), -1);
close(fd);
ASSERT_INT_NE(fd = open("/dev/null", 0), -1);
close(fd);
ASSERT_PTR_NE(f = fopen("\\d1\\j.txt", "r"), NULL);
fclose(f);
ASSERT_PTR_NE(f = fopen("/dev/null", "w"), NULL);
fclose(f);
ASSERT_INT_EQ(chdir("/"), 0);
ASSERT_INT_NE(fd = open("d1/j.txt", 0), -1);
close(fd);
ASSERT_PTR_NE(f = fopen("d1\\j.txt", "r"), NULL);
fclose(f);
ASSERT_INT_EQ(chdir("\\d1"), 0);
ASSERT_INT_NE(fd = open("j.txt", 0), -1);
close(fd);
ASSERT_PTR_NE(f = fopen("j.txt", "r"), NULL);
fclose(f);
TEST_DONE();
TEST_START("access world after chroot");
ASSERT_INT_EQ(chdir("/"), 0);
ASSERT_INT_EQ(fd = open(test_root, 0), -1);
ASSERT_INT_EQ(errno, ENOENT);
ASSERT_INT_EQ(fd = open("..\\", 0), -1);
ASSERT_INT_EQ(errno, EACCES);
ASSERT_INT_EQ(fd = open("../", 0), -1);
ASSERT_INT_EQ(errno, EACCES);
ASSERT_INT_EQ(fd = open("../outofjail.txt", O_CREAT), -1);
ASSERT_INT_EQ(errno, EACCES);
/* ensure outofjail.txt is not created by the above call*/
path[0] = '\0';
strcat(path, test_root);
strcat(path, "\\chroot-testdir\\outofjail.txt");
ASSERT_INT_EQ(fd = _open(path, 0), -1);
ASSERT_INT_EQ(errno, ENOENT);
ASSERT_INT_EQ(fd = open("world-jn\\w.Txt", 0), -1);
ASSERT_INT_EQ(errno, EACCES);
ASSERT_PTR_EQ(f = fopen("world-jn\\w.Txt", "r"), NULL);
ASSERT_INT_EQ(errno, EACCES);
ASSERT_INT_EQ(fd = open("world-sl\\w.Txt", 0), -1);
ASSERT_INT_EQ(errno, EACCES);
ASSERT_PTR_EQ(f = fopen("world-sl\\w.Txt", "r"), NULL);
ASSERT_INT_EQ(errno, EACCES);
TEST_DONE();
//_wsystem(L"RD /S /Q chroot-testdir >NUL 2>&1");
}
void
miscellaneous_tests()
{
@ -199,4 +320,5 @@ miscellaneous_tests()
test_pw();
test_realpath();
test_statvfs();
test_chroot();
}

View File

@ -17,14 +17,14 @@ extern void log_init(char *av0, int level, int facility, int on_stderr);
void
tests()
{
_set_abort_behavior(0, 1);
log_init(NULL, 7, 2, 0);
_set_abort_behavior(0, 1);
log_init(NULL, 7, 2, 0);
signal_tests();
socket_tests();
file_tests();
dir_tests();
str_tests();
miscellaneous_tests();
socket_tests();
file_tests();
dir_tests();
str_tests();
miscellaneous_tests();
}
char *

View File

@ -438,7 +438,7 @@ int register_child(void* child, unsigned long pid);
int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
int pipein[2], pipeout[2], pipeerr[2], r;
char *exec_command = NULL, *progdir = w32_programdir(), *cmd = NULL, *shell_host = NULL, *command_b64 = NULL;
wchar_t *exec_command_w = NULL, *pw_dir_w;
wchar_t *exec_command_w = NULL;
const char *sftp_exe = "sftp-server.exe", *argp = NULL;
size_t command_b64_len = 0;
PROCESS_INFORMATION pi;
@ -457,9 +457,6 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
if (pipe(pipein) == -1 || pipe(pipeout) == -1 || pipe(pipeerr) == -1)
fatal("%s: cannot create pipe: %.100s", __func__, strerror(errno));
if ((pw_dir_w = utf8_to_utf16(s->pw->pw_dir)) == NULL)
fatal("%s: out of memory", __func__);
set_nonblock(pipein[0]);
set_nonblock(pipein[1]);
set_nonblock(pipeout[0]);
@ -474,6 +471,9 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
/* setup Environment varibles */
setup_session_vars(s);
if (!in_chroot)
chdir(s->pw->pw_dir);
/* prepare exec - path used with CreateProcess() */
if (s->is_subsystem || (command && memcmp(command, "scp", 3) == 0)) {
/* relative or absolute */
@ -564,10 +564,9 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
debug("Executing command: %s", exec_command);
UTF8_TO_UTF16_FATAL(exec_command_w, exec_command);
/* in debug mode launch using sshd.exe user context */
create_process_ret_val = CreateProcessW(NULL, exec_command_w, NULL, NULL, TRUE,
DETACHED_PROCESS, NULL, pw_dir_w,
DETACHED_PROCESS, NULL, NULL,
&si, &pi);
if (!create_process_ret_val)
@ -598,8 +597,7 @@ int do_exec_windows(struct ssh *ssh, Session *s, const char *command, int pty) {
else
session_set_fds(ssh, s, pipein[1], pipeout[0], pipeerr[0], s->is_subsystem, 1); /* tty interactive session */
free(pw_dir_w);
free(exec_command_w);
free(exec_command_w);
return 0;
}
@ -1536,11 +1534,15 @@ safely_chroot(const char *path, uid_t uid)
char component[PATH_MAX];
struct stat st;
#ifdef WINDOWS
if (path[1] != ':')
#else
if (*path != '/')
#endif
fatal("chroot path does not begin at root");
if (strlen(path) >= sizeof(component))
fatal("chroot path too long");
#ifndef WINDOWS
/*
* Descend the path, checking that each component is a
* root-owned directory with strict permissions.
@ -1568,7 +1570,7 @@ safely_chroot(const char *path, uid_t uid)
cp == NULL ? "" : "component ", component);
}
#endif
if (chdir(path) == -1)
fatal("Unable to chdir to chroot path \"%s\": "
"%s", path, strerror(errno));

3
sshd.c
View File

@ -918,8 +918,9 @@ privsep_postauth(Authctxt *authctxt)
close(pmonitor->m_recvfd);
pmonitor->m_recvfd = PRIVSEP_MONITOR_FD;
monitor_recv_keystate(pmonitor);
do_setusercontext(authctxt->pw);
monitor_apply_keystate(pmonitor);
packet_set_authenticated();
skip: