Blame SOURCES/Fixed-path-validation-in-drive-channel.patch

dacebc
From 865ba07a0fd4fbc7a8203482411aacca3bbfbb9f Mon Sep 17 00:00:00 2001
dacebc
From: akallabeth <akallabeth@posteo.net>
dacebc
Date: Mon, 24 Oct 2022 10:41:55 +0200
dacebc
Subject: [PATCH] Fixed path validation in drive channel
dacebc
dacebc
Check that canonical path is a subpath of the shared directory
dacebc
dacebc
(cherry picked from commit 844c94e6d0438fa7bd8ff8d5513c3f69c3018b85)
dacebc
---
dacebc
 channels/drive/client/drive_file.c | 106 ++++++++++++++++++-----------
dacebc
 channels/drive/client/drive_file.h |   8 +--
dacebc
 channels/drive/client/drive_main.c |   8 +--
dacebc
 3 files changed, 73 insertions(+), 49 deletions(-)
dacebc
dacebc
diff --git a/channels/drive/client/drive_file.c b/channels/drive/client/drive_file.c
dacebc
index 305438593..1ea4ab9da 100644
dacebc
--- a/channels/drive/client/drive_file.c
dacebc
+++ b/channels/drive/client/drive_file.c
dacebc
@@ -34,6 +34,7 @@
dacebc
 #include <stdlib.h>
dacebc
 #include <string.h>
dacebc
 #include <time.h>
dacebc
+#include <assert.h>
dacebc
 
dacebc
 #include <winpr/wtypes.h>
dacebc
 #include <winpr/crt.h>
dacebc
@@ -61,10 +62,14 @@
dacebc
 	} while (0)
dacebc
 #endif
dacebc
 
dacebc
-static void drive_file_fix_path(WCHAR* path)
dacebc
+static BOOL drive_file_fix_path(WCHAR* path, size_t length)
dacebc
 {
dacebc
 	size_t i;
dacebc
-	size_t length = _wcslen(path);
dacebc
+
dacebc
+	if ((length == 0) || (length > UINT32_MAX))
dacebc
+		return FALSE;
dacebc
+
dacebc
+	assert(path);
dacebc
 
dacebc
 	for (i = 0; i < length; i++)
dacebc
 	{
dacebc
@@ -75,58 +79,82 @@ static void drive_file_fix_path(WCHAR* path)
dacebc
 #ifdef WIN32
dacebc
 
dacebc
 	if ((length == 3) && (path[1] == L':') && (path[2] == L'/'))
dacebc
-		return;
dacebc
+		return FALSE;
dacebc
 
dacebc
 #else
dacebc
 
dacebc
 	if ((length == 1) && (path[0] == L'/'))
dacebc
-		return;
dacebc
+		return FALSE;
dacebc
 
dacebc
 #endif
dacebc
 
dacebc
 	if ((length > 0) && (path[length - 1] == L'/'))
dacebc
 		path[length - 1] = L'\0';
dacebc
+
dacebc
+	return TRUE;
dacebc
 }
dacebc
 
dacebc
 static WCHAR* drive_file_combine_fullpath(const WCHAR* base_path, const WCHAR* path,
dacebc
-                                          size_t PathLength)
dacebc
+                                          size_t PathWCharLength)
dacebc
 {
dacebc
-	WCHAR* fullpath;
dacebc
-	size_t base_path_length;
dacebc
+	BOOL ok = FALSE;
dacebc
+	WCHAR* fullpath = NULL;
dacebc
+	size_t length;
dacebc
 
dacebc
-	if (!base_path || (!path && (PathLength > 0)))
dacebc
-		return NULL;
dacebc
+	if (!base_path || (!path && (PathWCharLength > 0)))
dacebc
+		goto fail;
dacebc
 
dacebc
-	base_path_length = _wcslen(base_path) * 2;
dacebc
-	fullpath = (WCHAR*)calloc(1, base_path_length + PathLength + sizeof(WCHAR));
dacebc
+	const size_t base_path_length = _wcsnlen(base_path, MAX_PATH);
dacebc
+	length = base_path_length + PathWCharLength + 1;
dacebc
+	fullpath = (WCHAR*)calloc(length, sizeof(WCHAR));
dacebc
 
dacebc
 	if (!fullpath)
dacebc
+		goto fail;
dacebc
+
dacebc
+	CopyMemory(fullpath, base_path, base_path_length * sizeof(WCHAR));
dacebc
+	if (path)
dacebc
+		CopyMemory(&fullpath[base_path_length], path, PathWCharLength * sizeof(WCHAR));
dacebc
+
dacebc
+	if (!drive_file_fix_path(fullpath, length))
dacebc
+		goto fail;
dacebc
+
dacebc
+	/* Ensure the path does not contain sequences like '..' */
dacebc
+	const WCHAR dotdot[] = { '.', '.', '\0' };
dacebc
+	if (_wcsstr(&fullpath[base_path_length], dotdot))
dacebc
 	{
dacebc
-		WLog_ERR(TAG, "malloc failed!");
dacebc
-		return NULL;
dacebc
+		char abuffer[MAX_PATH] = { 0 };
dacebc
+		ConvertFromUnicode(CP_UTF8, 0, &fullpath[base_path_length], -1, (char**)&abuffer,
dacebc
+		                   ARRAYSIZE(abuffer) - 1, NULL, NULL);
dacebc
+
dacebc
+		WLog_WARN(TAG, "[rdpdr] received invalid file path '%s' from server, aborting!",
dacebc
+		          &abuffer[base_path_length]);
dacebc
+		goto fail;
dacebc
 	}
dacebc
 
dacebc
-	CopyMemory(fullpath, base_path, base_path_length);
dacebc
-	if (path)
dacebc
-		CopyMemory((char*)fullpath + base_path_length, path, PathLength);
dacebc
-	drive_file_fix_path(fullpath);
dacebc
+	ok = TRUE;
dacebc
+fail:
dacebc
+	if (!ok)
dacebc
+	{
dacebc
+		free(fullpath);
dacebc
+		fullpath = NULL;
dacebc
+	}
dacebc
 	return fullpath;
dacebc
 }
dacebc
 
dacebc
 static BOOL drive_file_remove_dir(const WCHAR* path)
dacebc
 {
dacebc
-	WIN32_FIND_DATAW findFileData;
dacebc
+	WIN32_FIND_DATAW findFileData = { 0 };
dacebc
 	BOOL ret = TRUE;
dacebc
-	HANDLE dir;
dacebc
-	WCHAR* fullpath;
dacebc
-	WCHAR* path_slash;
dacebc
-	size_t base_path_length;
dacebc
+	HANDLE dir = INVALID_HANDLE_VALUE;
dacebc
+	WCHAR* fullpath = NULL;
dacebc
+	WCHAR* path_slash = NULL;
dacebc
+	size_t base_path_length = 0;
dacebc
 
dacebc
 	if (!path)
dacebc
 		return FALSE;
dacebc
 
dacebc
-	base_path_length = _wcslen(path) * 2;
dacebc
-	path_slash = (WCHAR*)calloc(1, base_path_length + sizeof(WCHAR) * 3);
dacebc
+	base_path_length = _wcslen(path);
dacebc
+	path_slash = (WCHAR*)calloc(base_path_length + 3, sizeof(WCHAR));
dacebc
 
dacebc
 	if (!path_slash)
dacebc
 	{
dacebc
@@ -134,12 +162,11 @@ static BOOL drive_file_remove_dir(const WCHAR* path)
dacebc
 		return FALSE;
dacebc
 	}
dacebc
 
dacebc
-	CopyMemory(path_slash, path, base_path_length);
dacebc
-	path_slash[base_path_length / 2] = L'/';
dacebc
-	path_slash[base_path_length / 2 + 1] = L'*';
dacebc
+	CopyMemory(path_slash, path, base_path_length * sizeof(WCHAR));
dacebc
+	path_slash[base_path_length] = L'/';
dacebc
+	path_slash[base_path_length + 1] = L'*';
dacebc
 	DEBUG_WSTR("Search in %s", path_slash);
dacebc
 	dir = FindFirstFileW(path_slash, &findFileData);
dacebc
-	path_slash[base_path_length / 2 + 1] = 0;
dacebc
 
dacebc
 	if (dir == INVALID_HANDLE_VALUE)
dacebc
 	{
dacebc
@@ -149,7 +176,7 @@ static BOOL drive_file_remove_dir(const WCHAR* path)
dacebc
 
dacebc
 	do
dacebc
 	{
dacebc
-		size_t len = _wcslen(findFileData.cFileName);
dacebc
+		const size_t len = _wcsnlen(findFileData.cFileName, ARRAYSIZE(findFileData.cFileName));
dacebc
 
dacebc
 		if ((len == 1 && findFileData.cFileName[0] == L'.') ||
dacebc
 		    (len == 2 && findFileData.cFileName[0] == L'.' && findFileData.cFileName[1] == L'.'))
dacebc
@@ -157,7 +184,7 @@ static BOOL drive_file_remove_dir(const WCHAR* path)
dacebc
 			continue;
dacebc
 		}
dacebc
 
dacebc
-		fullpath = drive_file_combine_fullpath(path_slash, findFileData.cFileName, len * 2);
dacebc
+		fullpath = drive_file_combine_fullpath(path_slash, findFileData.cFileName, len);
dacebc
 		DEBUG_WSTR("Delete %s", fullpath);
dacebc
 
dacebc
 		if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
dacebc
@@ -333,13 +360,13 @@ static BOOL drive_file_init(DRIVE_FILE* file)
dacebc
 	return file->file_handle != INVALID_HANDLE_VALUE;
dacebc
 }
dacebc
 
dacebc
-DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathLength, UINT32 id,
dacebc
-                           UINT32 DesiredAccess, UINT32 CreateDisposition, UINT32 CreateOptions,
dacebc
-                           UINT32 FileAttributes, UINT32 SharedAccess)
dacebc
+DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength,
dacebc
+                           UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition,
dacebc
+                           UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess)
dacebc
 {
dacebc
 	DRIVE_FILE* file;
dacebc
 
dacebc
-	if (!base_path || (!path && (PathLength > 0)))
dacebc
+	if (!base_path || (!path && (PathWCharLength > 0)))
dacebc
 		return NULL;
dacebc
 
dacebc
 	file = (DRIVE_FILE*)calloc(1, sizeof(DRIVE_FILE));
dacebc
@@ -359,7 +386,7 @@ DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 Pat
dacebc
 	file->CreateDisposition = CreateDisposition;
dacebc
 	file->CreateOptions = CreateOptions;
dacebc
 	file->SharedAccess = SharedAccess;
dacebc
-	drive_file_set_fullpath(file, drive_file_combine_fullpath(base_path, path, PathLength));
dacebc
+	drive_file_set_fullpath(file, drive_file_combine_fullpath(base_path, path, PathWCharLength));
dacebc
 
dacebc
 	if (!drive_file_init(file))
dacebc
 	{
dacebc
@@ -714,13 +741,10 @@ BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UIN
dacebc
 				return FALSE;
dacebc
 
dacebc
 			fullpath = drive_file_combine_fullpath(file->basepath, (WCHAR*)Stream_Pointer(input),
dacebc
-			                                       FileNameLength);
dacebc
+			                                       FileNameLength / sizeof(WCHAR));
dacebc
 
dacebc
 			if (!fullpath)
dacebc
-			{
dacebc
-				WLog_ERR(TAG, "drive_file_combine_fullpath failed!");
dacebc
 				return FALSE;
dacebc
-			}
dacebc
 
dacebc
 #ifdef _WIN32
dacebc
 
dacebc
@@ -759,7 +783,7 @@ BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UIN
dacebc
 }
dacebc
 
dacebc
 BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery,
dacebc
-                                const WCHAR* path, UINT32 PathLength, wStream* output)
dacebc
+                                const WCHAR* path, UINT32 PathWCharLength, wStream* output)
dacebc
 {
dacebc
 	size_t length;
dacebc
 	WCHAR* ent_path;
dacebc
@@ -773,7 +797,7 @@ BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYT
dacebc
 		if (file->find_handle != INVALID_HANDLE_VALUE)
dacebc
 			FindClose(file->find_handle);
dacebc
 
dacebc
-		ent_path = drive_file_combine_fullpath(file->basepath, path, PathLength);
dacebc
+		ent_path = drive_file_combine_fullpath(file->basepath, path, PathWCharLength);
dacebc
 		/* open new search handle and retrieve the first entry */
dacebc
 		file->find_handle = FindFirstFileW(ent_path, &file->find_data);
dacebc
 		free(ent_path);
dacebc
diff --git a/channels/drive/client/drive_file.h b/channels/drive/client/drive_file.h
dacebc
index ed789d6f0..6d3bd7045 100644
dacebc
--- a/channels/drive/client/drive_file.h
dacebc
+++ b/channels/drive/client/drive_file.h
dacebc
@@ -51,9 +51,9 @@ struct _DRIVE_FILE
dacebc
 	UINT32 CreateOptions;
dacebc
 };
dacebc
 
dacebc
-DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathLength, UINT32 id,
dacebc
-                           UINT32 DesiredAccess, UINT32 CreateDisposition, UINT32 CreateOptions,
dacebc
-                           UINT32 FileAttributes, UINT32 SharedAccess);
dacebc
+DRIVE_FILE* drive_file_new(const WCHAR* base_path, const WCHAR* path, UINT32 PathWCharLength,
dacebc
+                           UINT32 id, UINT32 DesiredAccess, UINT32 CreateDisposition,
dacebc
+                           UINT32 CreateOptions, UINT32 FileAttributes, UINT32 SharedAccess);
dacebc
 BOOL drive_file_free(DRIVE_FILE* file);
dacebc
 
dacebc
 BOOL drive_file_open(DRIVE_FILE* file);
dacebc
@@ -64,6 +64,6 @@ BOOL drive_file_query_information(DRIVE_FILE* file, UINT32 FsInformationClass, w
dacebc
 BOOL drive_file_set_information(DRIVE_FILE* file, UINT32 FsInformationClass, UINT32 Length,
dacebc
                                 wStream* input);
dacebc
 BOOL drive_file_query_directory(DRIVE_FILE* file, UINT32 FsInformationClass, BYTE InitialQuery,
dacebc
-                                const WCHAR* path, UINT32 PathLength, wStream* output);
dacebc
+                                const WCHAR* path, UINT32 PathWCharLength, wStream* output);
dacebc
 
dacebc
 #endif /* FREERDP_CHANNEL_DRIVE_FILE_H */
dacebc
diff --git a/channels/drive/client/drive_main.c b/channels/drive/client/drive_main.c
dacebc
index 1b5422522..d3776381c 100644
dacebc
--- a/channels/drive/client/drive_main.c
dacebc
+++ b/channels/drive/client/drive_main.c
dacebc
@@ -184,8 +184,8 @@ static UINT drive_process_irp_create(DRIVE_DEVICE* drive, IRP* irp)
dacebc
 
dacebc
 	path = (const WCHAR*)Stream_Pointer(irp->input);
dacebc
 	FileId = irp->devman->id_sequence++;
dacebc
-	file = drive_file_new(drive->path, path, PathLength, FileId, DesiredAccess, CreateDisposition,
dacebc
-	                      CreateOptions, FileAttributes, SharedAccess);
dacebc
+	file = drive_file_new(drive->path, path, PathLength / sizeof(WCHAR), FileId, DesiredAccess,
dacebc
+	                      CreateDisposition, CreateOptions, FileAttributes, SharedAccess);
dacebc
 
dacebc
 	if (!file)
dacebc
 	{
dacebc
@@ -636,8 +636,8 @@ static UINT drive_process_irp_query_directory(DRIVE_DEVICE* drive, IRP* irp)
dacebc
 		irp->IoStatus = STATUS_UNSUCCESSFUL;
dacebc
 		Stream_Write_UINT32(irp->output, 0); /* Length */
dacebc
 	}
dacebc
-	else if (!drive_file_query_directory(file, FsInformationClass, InitialQuery, path, PathLength,
dacebc
-	                                     irp->output))
dacebc
+	else if (!drive_file_query_directory(file, FsInformationClass, InitialQuery, path,
dacebc
+	                                     PathLength / sizeof(WCHAR), irp->output))
dacebc
 	{
dacebc
 		irp->IoStatus = drive_map_windows_err(GetLastError());
dacebc
 	}
dacebc
-- 
dacebc
2.37.1
dacebc