Commit message: Fixed double free
CVE: CVE-2020-4031
CWE: CWE-703
Vulnerability description: In FreeRDP before version 2.1.2, there is a use-after-free in gdi_SelectObject. All FreeRDP clients using compatibility mode with /relax-order-checks are affected. This is fixed in version 2.1.2.

Project: FreeRDP

rdpShadowServer* shadow_server_new(void)
{
	rdpShadowServer* server;
	server = (rdpShadowServer*)calloc(1, sizeof(rdpShadowServer));

	if (!server)
		return NULL;

	server->port = 3389;
	server->mayView = TRUE;
	server->mayInteract = TRUE;
	server->rfxMode = RLGR3;
	server->h264RateControlMode = H264_RATECONTROL_VBR;
	server->h264BitRate = 10000000;
	server->h264FrameRate = 30;
	server->h264QP = 0;
	server->authentication = FALSE;
	server->settings = freerdp_settings_new(FREERDP_SETTINGS_SERVER_MODE);
	return server;
}

Project: FreeRDP

int shadow_server_init(rdpShadowServer* server)
{
	int status;
	winpr_InitializeSSL(WINPR_SSL_INIT_DEFAULT);
	WTSRegisterWtsApiFunctionTable(FreeRDP_InitWtsApi());

	if (!(server->clients = ArrayList_New(TRUE)))
		goto fail_client_array;

	if (!(server->StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
		goto fail_stop_event;

	if (!InitializeCriticalSectionAndSpinCount(&(server->lock), 4000))
		goto fail_server_lock;

	status = shadow_server_init_config_path(server);

	if (status < 0)
		goto fail_config_path;

	status = shadow_server_init_certificate(server);

	if (status < 0)
		goto fail_certificate;

	server->listener = freerdp_listener_new();

	if (!server->listener)
		goto fail_listener;

	server->listener->info = (void*)server;
	server->listener->PeerAccepted = shadow_client_accepted;
	server->subsystem = shadow_subsystem_new();

	if (!server->subsystem)
		goto fail_subsystem_new;

	status = shadow_subsystem_init(server->subsystem, server);

	if (status >= 0)
		return status;

	shadow_subsystem_free(server->subsystem);
fail_subsystem_new:
	freerdp_listener_free(server->listener);
	server->listener = NULL;
fail_listener:
	free(server->CertificateFile);
	server->CertificateFile = NULL;
	free(server->PrivateKeyFile);
	server->PrivateKeyFile = NULL;
fail_certificate:
	free(server->ConfigPath);
	server->ConfigPath = NULL;
fail_config_path:
	DeleteCriticalSection(&(server->lock));
fail_server_lock:
	CloseHandle(server->StopEvent);
	server->StopEvent = NULL;
fail_stop_event:
	ArrayList_Free(server->clients);
	server->clients = NULL;
fail_client_array:
	WLog_ERR(TAG, "Failed to initialize shadow server");
	return -1;
}

Project: FreeRDP

int shadow_server_uninit(rdpShadowServer* server)
{
	if (!server)
		return -1;

	shadow_server_stop(server);
	shadow_subsystem_uninit(server->subsystem);
	shadow_subsystem_free(server->subsystem);
	freerdp_listener_free(server->listener);
	server->listener = NULL;
	free(server->CertificateFile);
	server->CertificateFile = NULL;
	free(server->PrivateKeyFile);
	server->PrivateKeyFile = NULL;
	free(server->ConfigPath);
	server->ConfigPath = NULL;
	DeleteCriticalSection(&(server->lock));
	CloseHandle(server->StopEvent);
	server->StopEvent = NULL;
	ArrayList_Free(server->clients);
	server->clients = NULL;
	return 1;
}

Project: FreeRDP

static int shadow_server_print_command_line_help(int argc, char** argv)
{
	char* str;
	size_t length;
	COMMAND_LINE_ARGUMENT_A* arg;
	COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(shadow_args)];
	memcpy(largs, shadow_args, sizeof(shadow_args));
	if (argc < 1)
		return -1;

	WLog_INFO(TAG, "Usage: %s [options]", argv[0]);
	WLog_INFO(TAG, "");
	WLog_INFO(TAG, "Syntax:");
	WLog_INFO(TAG, "    /flag (enables flag)");
	WLog_INFO(TAG, "    /option:<value> (specifies option with value)");
	WLog_INFO(TAG,
	          "    +toggle -toggle (enables or disables toggle, where '/' is a synonym of '+')");
	WLog_INFO(TAG, "");
	arg = largs;

	do
	{
		if (arg->Flags & COMMAND_LINE_VALUE_FLAG)
		{
			WLog_INFO(TAG, "    %s", "/");
			WLog_INFO(TAG, "%-20s", arg->Name);
			WLog_INFO(TAG, "\t%s", arg->Text);
		}
		else if ((arg->Flags & COMMAND_LINE_VALUE_REQUIRED) ||
		         (arg->Flags & COMMAND_LINE_VALUE_OPTIONAL))
		{
			WLog_INFO(TAG, "    %s", "/");

			if (arg->Format)
			{
				length = (strlen(arg->Name) + strlen(arg->Format) + 2);
				str = (char*)malloc(length + 1);

				if (!str)
					return -1;

				sprintf_s(str, length + 1, "%s:%s", arg->Name, arg->Format);
				WLog_INFO(TAG, "%-20s", str);
				free(str);
			}
			else
			{
				WLog_INFO(TAG, "%-20s", arg->Name);
			}

			WLog_INFO(TAG, "\t%s", arg->Text);
		}
		else if (arg->Flags & COMMAND_LINE_VALUE_BOOL)
		{
			length = strlen(arg->Name) + 32;
			str = (char*)malloc(length + 1);

			if (!str)
				return -1;

			sprintf_s(str, length + 1, "%s (default:%s)", arg->Name, arg->Default ? "on" : "off");
			WLog_INFO(TAG, "    %s", arg->Default ? "-" : "+");
			WLog_INFO(TAG, "%-20s", str);
			free(str);
			WLog_INFO(TAG, "\t%s", arg->Text);
		}
	} while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);

	return 1;
}

Project: FreeRDP

static BOOL shadow_server_init_certificate(rdpShadowServer* server)
{
	char* filepath;
	MAKECERT_CONTEXT* makecert = NULL;
	BOOL ret = FALSE;
	const char* makecert_argv[6] = { "makecert", "-rdp", "-live", "-silent", "-y", "5" };
	int makecert_argc = (sizeof(makecert_argv) / sizeof(char*));

	if (!PathFileExistsA(server->ConfigPath) && !PathMakePathA(server->ConfigPath, 0))
	{
		WLog_ERR(TAG, "Failed to create directory '%s'", server->ConfigPath);
		return FALSE;
	}

	if (!(filepath = GetCombinedPath(server->ConfigPath, "shadow")))
		return FALSE;

	if (!PathFileExistsA(filepath) && !PathMakePathA(filepath, 0))
	{
		if (!CreateDirectoryA(filepath, 0))
		{
			WLog_ERR(TAG, "Failed to create directory '%s'", filepath);
			goto out_fail;
		}
	}

	server->CertificateFile = GetCombinedPath(filepath, "shadow.crt");
	server->PrivateKeyFile = GetCombinedPath(filepath, "shadow.key");

	if (!server->CertificateFile || !server->PrivateKeyFile)
		goto out_fail;

	if ((!PathFileExistsA(server->CertificateFile)) || (!PathFileExistsA(server->PrivateKeyFile)))
	{
		makecert = makecert_context_new();

		if (!makecert)
			goto out_fail;

		if (makecert_context_process(makecert, makecert_argc, (char**)makecert_argv) < 0)
			goto out_fail;

		if (makecert_context_set_output_file_name(makecert, "shadow") != 1)
			goto out_fail;

		if (!PathFileExistsA(server->CertificateFile))
		{
			if (makecert_context_output_certificate_file(makecert, filepath) != 1)
				goto out_fail;
		}

		if (!PathFileExistsA(server->PrivateKeyFile))
		{
			if (makecert_context_output_private_key_file(makecert, filepath) != 1)
				goto out_fail;
		}
	}

	ret = TRUE;
out_fail:
	makecert_context_free(makecert);
	free(filepath);
	return ret;
}

Project: FreeRDP

int shadow_server_start(rdpShadowServer* server)
{
	BOOL ipc;
	BOOL status;
	WSADATA wsaData;

	if (!server)
		return -1;

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return -1;

#ifndef _WIN32
	signal(SIGPIPE, SIG_IGN);
#endif
	server->screen = shadow_screen_new(server);

	if (!server->screen)
	{
		WLog_ERR(TAG, "screen_new failed");
		return -1;
	}

	server->capture = shadow_capture_new(server);

	if (!server->capture)
	{
		WLog_ERR(TAG, "capture_new failed");
		return -1;
	}

	/* Bind magic:
	 *
	 * emtpy                 ... bind TCP all
	 * <local path>          ... bind local (IPC)
	 * bind-socket,<address> ... bind TCP to specified interface
	 */
	ipc = server->ipcSocket && (strncmp(bind_address, server->ipcSocket,
	                                    strnlen(bind_address, sizeof(bind_address))) != 0);
	if (!ipc)
	{
		size_t x, count;
		char** list = CommandLineParseCommaSeparatedValuesEx(NULL, server->ipcSocket, &count);
		if (!list || (count <= 1))
		{
			if (server->ipcSocket == NULL)
			{
				if (!open_port(server, NULL))
				{
					free(list);
					return -1;
				}
			}
			else
			{
				free(list);
				return -1;
			}
		}

		for (x = 1; x < count; x++)
		{
			BOOL success = open_port(server, list[x]);
			if (!success)
			{
				free(list);
				return -1;
			}
		}
		free(list);
	}
	else
	{
		status = server->listener->OpenLocal(server->listener, server->ipcSocket);
		if (!status)
		{
			WLog_ERR(TAG, "Problem creating local socket listener. (Port already used or "
			              "insufficient permissions?)");
			return -1;
		}
	}

	if (!(server->thread = CreateThread(NULL, 0, shadow_server_thread, (void*)server, 0, NULL)))
	{
		return -1;
	}

	return 0;
}

Project: FreeRDP

int shadow_server_stop(rdpShadowServer* server)
{
	if (!server)
		return -1;

	if (server->thread)
	{
		SetEvent(server->StopEvent);
		WaitForSingleObject(server->thread, INFINITE);
		CloseHandle(server->thread);
		server->thread = NULL;
		server->listener->Close(server->listener);
	}

	if (server->screen)
	{
		shadow_screen_free(server->screen);
		server->screen = NULL;
	}

	if (server->capture)
	{
		shadow_capture_free(server->capture);
		server->capture = NULL;
	}

	return 0;
}

Project: FreeRDP

void shadow_server_free(rdpShadowServer* server)
{
	if (!server)
		return;

	free(server->ipcSocket);
	server->ipcSocket = NULL;
	freerdp_settings_free(server->settings);
	server->settings = NULL;
	free(server);
}

Project: FreeRDP

static BOOL open_port(rdpShadowServer* server, char* address)
{
	BOOL status;
	char* modaddr = address;

	if (modaddr)
	{
		if (modaddr[0] == '[')
		{
			char* end = strchr(address, ']');
			if (!end)
			{
				WLog_ERR(TAG, "Could not parse bind-address %s", address);
				return -1;
			}
			*end++ = '\0';
			if (strlen(end) > 0)
			{
				WLog_ERR(TAG, "Excess data after IPv6 address: '%s'", end);
				return -1;
			}
			modaddr++;
		}
	}
	status = server->listener->Open(server->listener, modaddr, (UINT16)server->port);

	if (!status)
	{
		WLog_ERR(TAG,
		         "Problem creating TCP listener. (Port already used or insufficient permissions?)");
	}

	return status;
}

Project: FreeRDP

int shadow_server_parse_command_line(rdpShadowServer* server, int argc, char** argv)
{
	int status;
	DWORD flags;
	COMMAND_LINE_ARGUMENT_A* arg;
	rdpSettings* settings = server->settings;
	COMMAND_LINE_ARGUMENT_A largs[ARRAYSIZE(shadow_args)];
	memcpy(largs, shadow_args, sizeof(shadow_args));

	if (argc < 2)
		return 1;

	CommandLineClearArgumentsA(largs);
	flags = COMMAND_LINE_SEPARATOR_COLON;
	flags |= COMMAND_LINE_SIGIL_SLASH | COMMAND_LINE_SIGIL_PLUS_MINUS;
	status = CommandLineParseArgumentsA(argc, argv, largs, flags, server, NULL, NULL);

	if (status < 0)
		return status;

	arg = largs;
	errno = 0;

	do
	{
		if (!(arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
			continue;

		CommandLineSwitchStart(arg) CommandLineSwitchCase(arg, "port")
		{
			long val = strtol(arg->Value, NULL, 0);

			if ((errno != 0) || (val <= 0) || (val > UINT16_MAX))
				return -1;

			server->port = (DWORD)val;
		}
		CommandLineSwitchCase(arg, "ipc-socket")
		{
			/* /bind-address is incompatible */
			if (server->ipcSocket)
				return -1;

			server->ipcSocket = _strdup(arg->Value);

			if (!server->ipcSocket)
				return -1;
		}
		CommandLineSwitchCase(arg, "bind-address")
		{
			int rc;
			size_t len = strlen(arg->Value) + sizeof(bind_address);
			/* /ipc-socket is incompatible */
			if (server->ipcSocket)
				return -1;
			server->ipcSocket = calloc(len, sizeof(CHAR));

			if (!server->ipcSocket)
				return -1;

			rc = _snprintf(server->ipcSocket, len, "%s%s", bind_address, arg->Value);
			if ((rc < 0) || ((size_t)rc != len - 1))
				return -1;
		}
		CommandLineSwitchCase(arg, "may-view")
		{
			server->mayView = arg->Value ? TRUE : FALSE;
		}
		CommandLineSwitchCase(arg, "may-interact")
		{
			server->mayInteract = arg->Value ? TRUE : FALSE;
		}
		CommandLineSwitchCase(arg, "rect")
		{
			char* p;
			char* tok[4];
			long x = -1, y = -1, w = -1, h = -1;
			char* str = _strdup(arg->Value);

			if (!str)
				return -1;

			tok[0] = p = str;
			p = strchr(p + 1, ',');

			if (!p)
			{
				free(str);
				return -1;
			}

			*p++ = '\0';
			tok[1] = p;
			p = strchr(p + 1, ',');

			if (!p)
			{
				free(str);
				return -1;
			}

			*p++ = '\0';
			tok[2] = p;
			p = strchr(p + 1, ',');

			if (!p)
			{
				free(str);
				return -1;
			}

			*p++ = '\0';
			tok[3] = p;
			x = strtol(tok[0], NULL, 0);

			if (errno != 0)
				goto fail;

			y = strtol(tok[1], NULL, 0);

			if (errno != 0)
				goto fail;

			w = strtol(tok[2], NULL, 0);

			if (errno != 0)
				goto fail;

			h = strtol(tok[3], NULL, 0);

			if (errno != 0)
				goto fail;

		fail:
			free(str);

			if ((x < 0) || (y < 0) || (w < 1) || (h < 1) || (errno != 0))
				return -1;

			server->subRect.left = x;
			server->subRect.top = y;
			server->subRect.right = x + w;
			server->subRect.bottom = y + h;
			server->shareSubRect = TRUE;
		}
		CommandLineSwitchCase(arg, "auth")
		{
			server->authentication = arg->Value ? TRUE : FALSE;
		}
		CommandLineSwitchCase(arg, "sec")
		{
			if (strcmp("rdp", arg->Value) == 0) /* Standard RDP */
			{
				settings->RdpSecurity = TRUE;
				settings->TlsSecurity = FALSE;
				settings->NlaSecurity = FALSE;
				settings->ExtSecurity = FALSE;
				settings->UseRdpSecurityLayer = TRUE;
			}
			else if (strcmp("tls", arg->Value) == 0) /* TLS */
			{
				settings->RdpSecurity = FALSE;
				settings->TlsSecurity = TRUE;
				settings->NlaSecurity = FALSE;
				settings->ExtSecurity = FALSE;
			}
			else if (strcmp("nla", arg->Value) == 0) /* NLA */
			{
				settings->RdpSecurity = FALSE;
				settings->TlsSecurity = FALSE;
				settings->NlaSecurity = TRUE;
				settings->ExtSecurity = FALSE;
			}
			else if (strcmp("ext", arg->Value) == 0) /* NLA Extended */
			{
				settings->RdpSecurity = FALSE;
				settings->TlsSecurity = FALSE;
				settings->NlaSecurity = FALSE;
				settings->ExtSecurity = TRUE;
			}
			else
			{
				WLog_ERR(TAG, "unknown protocol security: %s", arg->Value);
			}
		}
		CommandLineSwitchCase(arg, "sec-rdp")
		{
			settings->RdpSecurity = arg->Value ? TRUE : FALSE;
		}
		CommandLineSwitchCase(arg, "sec-tls")
		{
			settings->TlsSecurity = arg->Value ? TRUE : FALSE;
		}
		CommandLineSwitchCase(arg, "sec-nla")
		{
			settings->NlaSecurity = arg->Value ? TRUE : FALSE;
		}
		CommandLineSwitchCase(arg, "sec-ext")
		{
			settings->ExtSecurity = arg->Value ? TRUE : FALSE;
		}
		CommandLineSwitchCase(arg, "sam-file")
		{
			freerdp_settings_set_string(settings, FreeRDP_NtlmSamFile, arg->Value);
		}
		CommandLineSwitchDefault(arg)
		{
		}
		CommandLineSwitchEnd(arg)
	} while ((arg = CommandLineFindNextArgumentA(arg)) != NULL);

	arg = CommandLineFindArgumentA(largs, "monitors");

	if (arg && (arg->Flags & COMMAND_LINE_ARGUMENT_PRESENT))
	{
		int index;
		int numMonitors;
		MONITOR_DEF monitors[16];
		numMonitors = shadow_enum_monitors(monitors, 16);

		if (arg->Flags & COMMAND_LINE_VALUE_PRESENT)
		{
			/* Select monitors */
			long val = strtol(arg->Value, NULL, 0);

			if ((val < 0) || (errno != 0) || (val >= numMonitors))
				status = COMMAND_LINE_STATUS_PRINT;

			server->selectedMonitor = val;
		}
		else
		{
			int width, height;
			MONITOR_DEF* monitor;

			/* List monitors */

			for (index = 0; index < numMonitors; index++)
			{
				monitor = &monitors[index];
				width = monitor->right - monitor->left;
				height = monitor->bottom - monitor->top;
				WLog_INFO(TAG, "      %s [%d] %dx%d\t+%" PRId32 "+%" PRId32 "",
				          (monitor->flags == 1) ? "*" : " ", index, width, height, monitor->left,
				          monitor->top);
			}

			status = COMMAND_LINE_STATUS_PRINT;
		}
	}

	return status;
}

Project: FreeRDP

int shadow_server_command_line_status_print(rdpShadowServer* server, int argc, char** argv,
                                            int status)
{
	WINPR_UNUSED(server);

	if (status == COMMAND_LINE_STATUS_PRINT_VERSION)
	{
		WLog_INFO(TAG, "FreeRDP version %s (git %s)", FREERDP_VERSION_FULL, GIT_REVISION);
		return COMMAND_LINE_STATUS_PRINT_VERSION;
	}
	else if (status == COMMAND_LINE_STATUS_PRINT_BUILDCONFIG)
	{
		WLog_INFO(TAG, "%s", freerdp_get_build_config());
		return COMMAND_LINE_STATUS_PRINT_BUILDCONFIG;
	}
	else if (status == COMMAND_LINE_STATUS_PRINT)
	{
		return COMMAND_LINE_STATUS_PRINT;
	}
	else if (status < 0)
	{
		if (shadow_server_print_command_line_help(argc, argv) < 0)
			return -1;

		return COMMAND_LINE_STATUS_PRINT_HELP;
	}

	return 1;
}

Project: FreeRDP

static int shadow_server_init_config_path(rdpShadowServer* server)
{
#ifdef _WIN32

	if (!server->ConfigPath)
	{
		server->ConfigPath = GetEnvironmentSubPath("LOCALAPPDATA", "freerdp");
	}

#endif
#ifdef __APPLE__

	if (!server->ConfigPath)
	{
		char* userLibraryPath;
		char* userApplicationSupportPath;
		userLibraryPath = GetKnownSubPath(KNOWN_PATH_HOME, "Library");

		if (userLibraryPath)
		{
			if (!PathFileExistsA(userLibraryPath) && !PathMakePathA(userLibraryPath, 0))
			{
				WLog_ERR(TAG, "Failed to create directory '%s'", userLibraryPath);
				free(userLibraryPath);
				return -1;
			}

			userApplicationSupportPath = GetCombinedPath(userLibraryPath, "Application Support");

			if (userApplicationSupportPath)
			{
				if (!PathFileExistsA(userApplicationSupportPath) &&
				    !PathMakePathA(userApplicationSupportPath, 0))
				{
					WLog_ERR(TAG, "Failed to create directory '%s'", userApplicationSupportPath);
					free(userLibraryPath);
					free(userApplicationSupportPath);
					return -1;
				}

				server->ConfigPath = GetCombinedPath(userApplicationSupportPath, "freerdp");
			}

			free(userLibraryPath);
			free(userApplicationSupportPath);
		}
	}

#endif

	if (!server->ConfigPath)
	{
		char* configHome;
		configHome = GetKnownPath(KNOWN_PATH_XDG_CONFIG_HOME);

		if (configHome)
		{
			if (!PathFileExistsA(configHome) && !PathMakePathA(configHome, 0))
			{
				WLog_ERR(TAG, "Failed to create directory '%s'", configHome);
				free(configHome);
				return -1;
			}

			server->ConfigPath = GetKnownSubPath(KNOWN_PATH_XDG_CONFIG_HOME, "freerdp");
			free(configHome);
		}
	}

	if (!server->ConfigPath)
		return -1; /* no usable config path */

	return 1;
}

Project: FreeRDP

static DWORD WINAPI shadow_server_thread(LPVOID arg)
{
	rdpShadowServer* server = (rdpShadowServer*)arg;
	BOOL running = TRUE;
	DWORD status;
	freerdp_listener* listener = server->listener;
	shadow_subsystem_start(server->subsystem);

	while (running)
	{
		HANDLE events[32];
		DWORD nCount = 0;
		events[nCount++] = server->StopEvent;
		nCount += listener->GetEventHandles(listener, &events[nCount], 32 - nCount);

		if (nCount <= 1)
		{
			WLog_ERR(TAG, "Failed to get FreeRDP file descriptor");
			break;
		}

		status = WaitForMultipleObjects(nCount, events, FALSE, INFINITE);

		switch (status)
		{
			case WAIT_FAILED:
			case WAIT_OBJECT_0:
				running = FALSE;
				break;

			default:
			{
				if (!listener->CheckFileDescriptor(listener))
				{
					WLog_ERR(TAG, "Failed to check FreeRDP file descriptor");
					running = FALSE;
				}
				else
				{
#ifdef _WIN32
					Sleep(100); /* FIXME: listener event handles */
#endif
				}
			}
			break;
		}
	}

	listener->Close(listener);
	shadow_subsystem_stop(server->subsystem);

	/* Signal to the clients that server is being stopped and wait for them
	 * to disconnect. */
	if (shadow_client_boardcast_quit(server, 0))
	{
		while (ArrayList_Count(server->clients) > 0)
		{
			Sleep(100);
		}
	}

	ExitThread(0);
	return 0;
}