diff --git a/contrib/win32/win32compat/fileio.c b/contrib/win32/win32compat/fileio.c index 5719691e4..9148776f4 100644 --- a/contrib/win32/win32compat/fileio.c +++ b/contrib/win32/win32compat/fileio.c @@ -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) diff --git a/contrib/win32/win32compat/inc/dirent.h b/contrib/win32/win32compat/inc/dirent.h index 8ac3a598b..d1441a6c9 100644 --- a/contrib/win32/win32compat/inc/dirent.h +++ b/contrib/win32/win32compat/inc/dirent.h @@ -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 \ No newline at end of file diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 3bcf5f336..0cb4a72cd 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -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, §orsPerCluster, &bytesPerSector, - &freeClusters, &totalClusters) == TRUE)) { + if (path_utf16 == NULL) + return -1; + + if (GetDiskFreeSpaceW(path_utf16, §orsPerCluster, &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; +} diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h index f1082b63a..0f9bb1fdd 100644 --- a/contrib/win32/win32compat/misc_internal.h +++ b/contrib/win32/win32compat/misc_internal.h @@ -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 *); \ No newline at end of file +int is_absolute_path(const char *); +int file_in_chroot_jail(HANDLE, const char*); \ No newline at end of file diff --git a/contrib/win32/win32compat/no-ops.c b/contrib/win32/win32compat/no-ops.c index 7ecfb2d91..a179d0ddf 100644 --- a/contrib/win32/win32compat/no-ops.c +++ b/contrib/win32/win32compat/no-ops.c @@ -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) { diff --git a/contrib/win32/win32compat/pwd.c b/contrib/win32/win32compat/pwd.c index 118e298d8..d4ccadaae 100644 --- a/contrib/win32/win32compat/pwd.c +++ b/contrib/win32/win32compat/pwd.c @@ -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 diff --git a/contrib/win32/win32compat/shell-host.c b/contrib/win32/win32compat/shell-host.c index d6beafcbf..0758ea87c 100644 --- a/contrib/win32/win32compat/shell-host.c +++ b/contrib/win32/win32compat/shell-host.c @@ -318,6 +318,11 @@ STARTUPINFO inputSi; goto cleanup; \ } while(0) +void +debug3(const char *s, ...) { + return; +} + int ConSRWidth() { diff --git a/contrib/win32/win32compat/utf.c b/contrib/win32/win32compat/utf.c index bd93d42bc..340de7bc5 100644 --- a/contrib/win32/win32compat/utf.c +++ b/contrib/win32/win32compat/utf.c @@ -30,7 +30,9 @@ #include #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; } diff --git a/contrib/win32/win32compat/w32-sshfileperm.c b/contrib/win32/win32compat/w32-sshfileperm.c index 2dda4adf4..62e7bcb89 100644 --- a/contrib/win32/win32compat/w32-sshfileperm.c +++ b/contrib/win32/win32compat/w32-sshfileperm.c @@ -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; } diff --git a/contrib/win32/win32compat/w32fd.c b/contrib/win32/win32compat/w32fd.c index 371e58676..d836090fe 100644 --- a/contrib/win32/win32compat/w32fd.c +++ b/contrib/win32/win32compat/w32fd.c @@ -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) diff --git a/contrib/win32/win32compat/win32_dirent.c b/contrib/win32/win32compat/win32_dirent.c index e04125311..87202ef66 100644 --- a/contrib/win32/win32compat/win32_dirent.c +++ b/contrib/win32/win32compat/win32_dirent.c @@ -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); diff --git a/platform.c b/platform.c index 18c7751de..7fd872172 100644 --- a/platform.c +++ b/platform.c @@ -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 diff --git a/regress/unittests/win32compat/miscellaneous_tests.c b/regress/unittests/win32compat/miscellaneous_tests.c index 746c1a5dd..7f54023b2 100644 --- a/regress/unittests/win32compat/miscellaneous_tests.c +++ b/regress/unittests/win32compat/miscellaneous_tests.c @@ -1,4 +1,4 @@ -#include "includes.h" +#include "includes.h" #include #include #include @@ -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(); } diff --git a/regress/unittests/win32compat/tests.c b/regress/unittests/win32compat/tests.c index fac6e4f7c..588e2dc77 100644 --- a/regress/unittests/win32compat/tests.c +++ b/regress/unittests/win32compat/tests.c @@ -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 * diff --git a/session.c b/session.c index 78c091d0a..c8f4b8e5c 100644 --- a/session.c +++ b/session.c @@ -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)); diff --git a/sshd.c b/sshd.c index 24943ef84..ed8c15917 100644 --- a/sshd.c +++ b/sshd.c @@ -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: