Commit message: WindowsServiceControl: quote service binary path

Fix unquoted service path vulnerability.

Closes #657.
CVE: CVE-2020-15261
CWE: CWE-295
Vulnerability description: On Windows the Veyon Service before version 4.4.2 contains an unquoted service path vulnerability, allowing locally authenticated users with administrative privileges to run malicious executables with LocalSystem privileges. Since Veyon users (both students and teachers) usually don't have administrative privileges, this vulnerability is only dangerous in anyway unsafe setups. The problem has been fixed in version 4.4.2. As a workaround, the exploitation of the vulnerability can be prevented by revoking administrative privileges from all potentially untrustworthy users.

Project: veyon
File: WindowsServiceControl.cpp

bool WindowsServiceControl::uninstall()
{
	if( checkService() == false )
	{
		return false;
	}

	if( stop() == false )
	{
		return false;
	}

	if( DeleteService( m_serviceHandle ) == false )
	{
		vCritical() << qUtf8Printable( tr( "The service \"%1\" could not be uninstalled." ).arg( m_name ) );
		return false;
	}

	vInfo() << qUtf8Printable( tr( "The service \"%1\" has been uninstalled successfully." ).arg( m_name ) );

	return true;
}

Project: veyon
File: WindowsServiceControl.cpp

bool WindowsServiceControl::checkService() const
{
	if( m_serviceHandle == nullptr )
	{
		vCritical() << qUtf8Printable( tr( "Service \"%1\" could not be found." ).arg( m_name ) );
		return false;
	}

	return true;
}

Project: veyon
File: WindowsServiceControl.cpp

bool WindowsServiceControl::isRegistered()
{
	return m_serviceHandle != nullptr;
}

Project: veyon
File: WindowsServiceControl.cpp

WindowsServiceControl::~WindowsServiceControl()
{
	CloseServiceHandle( m_serviceHandle );
	CloseServiceHandle( m_serviceManager );
}

Project: veyon
File: WindowsServiceControl.cpp

bool WindowsServiceControl::setStartType( int startType )
{
	if( checkService() == false || startType == InvalidStartType )
	{
		return false;
	}

	if( ChangeServiceConfig( m_serviceHandle,
							 SERVICE_NO_CHANGE,	// dwServiceType
							 static_cast<DWORD>( startType ),
							 SERVICE_NO_CHANGE,	// dwErrorControl
							 nullptr,	// lpBinaryPathName
							 nullptr,	// lpLoadOrderGroup
							 nullptr,	// lpdwTagId
							 nullptr,	// lpDependencies
							 nullptr,	// lpServiceStartName
							 nullptr,	// lpPassword
							 nullptr	// lpDisplayName
							 ) == false )
	{
		vCritical() << qUtf8Printable( tr( "The start type of service \"%1\" could not be changed." ).arg( m_name ) );
		return false;
	}

	return true;
}

Project: veyon
File: WindowsServiceControl.cpp

bool WindowsServiceControl::stop()
{
	if( checkService() == false )
	{
		return false;
	}

	SERVICE_STATUS status;

	// Try to stop the service
	if( ControlService( m_serviceHandle, SERVICE_CONTROL_STOP, &status ) )
	{
		while( QueryServiceStatus( m_serviceHandle, &status ) )
		{
			if( status.dwCurrentState == SERVICE_STOP_PENDING )
			{
				Sleep( 1000 );
			}
			else
			{
				break;
			}
		}

		if( status.dwCurrentState != SERVICE_STOPPED )
		{
			vWarning() << "service" << m_name << "could not be stopped.";
			return false;
		}
	}

	return true;
}

Project: veyon
File: WindowsServiceControl.cpp

bool WindowsServiceControl::start()
{
	if( checkService() == false )
	{
		return false;
	}

	SERVICE_STATUS status;
	status.dwCurrentState = SERVICE_START_PENDING;

	if( StartService( m_serviceHandle, 0, nullptr ) )
	{
		while( QueryServiceStatus( m_serviceHandle, &status ) )
		{
			if( status.dwCurrentState == SERVICE_START_PENDING )
			{
				Sleep( 1000 );
			}
			else
			{
				break;
			}
		}
	}

	if( status.dwCurrentState != SERVICE_RUNNING )
	{
		vWarning() << "service" << m_name << "could not be started.";
		return false;
	}

	return true;
}

Project: veyon
File: WindowsServiceControl.cpp

WindowsServiceControl::WindowsServiceControl( const QString& name ) :
	m_name( name ),
	m_serviceManager( nullptr ),
	m_serviceHandle( nullptr )
{
	m_serviceManager = OpenSCManager( nullptr, nullptr, SC_MANAGER_ALL_ACCESS );

	if( m_serviceManager )
	{
		m_serviceHandle = OpenService( m_serviceManager, WindowsCoreFunctions::toConstWCharArray( m_name ),
									   SERVICE_ALL_ACCESS );
		if( m_serviceHandle == nullptr )
		{
			vCritical() << "could not open service" << m_name;
		}
	}
	else
	{
		vCritical() << "the Service Control Manager could not be contacted - service " << m_name << "can't be controlled.";
	}
}

Project: veyon
File: WindowsServiceControl.cpp

int WindowsServiceControl::startType()
{
	if( checkService() == false )
	{
		return InvalidStartType;
	}

	LPQUERY_SERVICE_CONFIG serviceConfig{nullptr};
	DWORD bufferSize = 0;
	DWORD bytesNeeded = 0;

	if( QueryServiceConfig( m_serviceHandle, nullptr, 0, &bytesNeeded ) == false )
	{
		if( GetLastError() == ERROR_INSUFFICIENT_BUFFER )
		{
			bufferSize = bytesNeeded;
			serviceConfig = LPQUERY_SERVICE_CONFIG(LocalAlloc(LMEM_FIXED, bufferSize));
		}
		else
		{
			return InvalidStartType;
		}
	}
	else
	{
		return InvalidStartType;
	}

	if( QueryServiceConfig( m_serviceHandle, serviceConfig, bufferSize, &bytesNeeded ) == false )
	{
		const auto error = GetLastError();
		vCritical() << error;
		LocalFree( serviceConfig );
		return InvalidStartType;
	}

	const auto startType = serviceConfig->dwStartType;

	LocalFree( serviceConfig );

	return startType;
}

Project: veyon
File: WindowsServiceControl.cpp

bool WindowsServiceControl::isRunning()
{
	if( checkService() == false )
	{
		return false;
	}

	SERVICE_STATUS status;
	if( QueryServiceStatus( m_serviceHandle, &status ) )
	{
		return status.dwCurrentState == SERVICE_RUNNING;
	}

	return false;
}

Project: veyon
File: WindowsServiceControl.cpp

bool WindowsServiceControl::install( const QString& filePath, const QString& displayName  )
{
	const auto binaryPath = QStringLiteral("\"%1\"").arg( QString( filePath ).replace( QLatin1Char('"'), QString() ) );

	m_serviceHandle = CreateService(
				m_serviceManager,		// SCManager database
				WindowsCoreFunctions::toConstWCharArray( m_name ),	// name of service
				WindowsCoreFunctions::toConstWCharArray( displayName ),// name to display
				SERVICE_ALL_ACCESS,	// desired access
				SERVICE_WIN32_OWN_PROCESS,
				// service type
				SERVICE_AUTO_START,	// start type
				SERVICE_ERROR_NORMAL,	// error control type
				WindowsCoreFunctions::toConstWCharArray( binaryPath ),		// service's binary
				nullptr,			// no load ordering group
				nullptr,			// no tag identifier
				L"Tcpip\0RpcSs\0\0",		// dependencies
				nullptr,			// LocalSystem account
				nullptr );			// no password

	if( m_serviceHandle == nullptr )
	{
		const auto error = GetLastError();
		if( error == ERROR_SERVICE_EXISTS )
		{
			vCritical() << qUtf8Printable( tr( "The service \"%1\" is already installed." ).arg( m_name ) );
		}
		else
		{
			vCritical() << qUtf8Printable( tr( "The service \"%1\" could not be installed." ).arg( m_name ) );
		}

		return false;
	}

	SC_ACTION serviceActions;
	serviceActions.Delay = 10000;
	serviceActions.Type = SC_ACTION_RESTART;

	SERVICE_FAILURE_ACTIONS serviceFailureActions;
	serviceFailureActions.dwResetPeriod = 0;
	serviceFailureActions.lpRebootMsg = nullptr;
	serviceFailureActions.lpCommand = nullptr;
	serviceFailureActions.lpsaActions = &serviceActions;
	serviceFailureActions.cActions = 1;
	ChangeServiceConfig2( m_serviceHandle, SERVICE_CONFIG_FAILURE_ACTIONS, &serviceFailureActions );

	// Everything went fine
	vInfo() << qUtf8Printable( tr( "The service \"%1\" has been installed successfully." ).arg( m_name ) );

	return true;
}