From 18fb059ddc4c271fba0b1a0077d42632b99ff8e9 Mon Sep 17 00:00:00 2001 From: dbuechel Date: Mon, 6 Jan 2020 15:11:57 +0100 Subject: [PATCH] SEBWIN-316: Implemented rudimentary VM detection. --- SafeExamBrowser.I18n.Contracts/TextKey.cs | 3 + SafeExamBrowser.I18n/Text.xml | 9 ++ .../VirtualMachineOperationTests.cs | 125 +++++++++++++++++- SafeExamBrowser.Runtime/CompositionRoot.cs | 3 +- .../Operations/VirtualMachineOperation.cs | 45 ++++++- .../IVirtualMachineDetector.cs | 21 +++ ...mBrowser.SystemComponents.Contracts.csproj | 1 + .../SafeExamBrowser.SystemComponents.csproj | 1 + .../SystemInfo.cs | 4 +- .../VirtualMachineDetector.cs | 41 ++++++ 10 files changed, 240 insertions(+), 13 deletions(-) create mode 100644 SafeExamBrowser.SystemComponents.Contracts/IVirtualMachineDetector.cs create mode 100644 SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs diff --git a/SafeExamBrowser.I18n.Contracts/TextKey.cs b/SafeExamBrowser.I18n.Contracts/TextKey.cs index 5e9dbdb1..30447485 100644 --- a/SafeExamBrowser.I18n.Contracts/TextKey.cs +++ b/SafeExamBrowser.I18n.Contracts/TextKey.cs @@ -88,6 +88,8 @@ namespace SafeExamBrowser.I18n.Contracts MessageBox_StartupErrorTitle, MessageBox_UnexpectedConfigurationError, MessageBox_UnexpectedConfigurationErrorTitle, + MessageBox_VirtualMachineNotAllowed, + MessageBox_VirtualMachineNotAllowedTitle, MessageBox_YesButton, Notification_AboutTooltip, Notification_LogTooltip, @@ -117,6 +119,7 @@ namespace SafeExamBrowser.I18n.Contracts OperationStatus_StopMouseInterception, OperationStatus_TerminateBrowser, OperationStatus_TerminateShell, + OperationStatus_ValidateVirtualMachinePolicy, OperationStatus_WaitExplorerStartup, OperationStatus_WaitExplorerTermination, OperationStatus_WaitRuntimeDisconnection, diff --git a/SafeExamBrowser.I18n/Text.xml b/SafeExamBrowser.I18n/Text.xml index 0bf19976..3524a0eb 100644 --- a/SafeExamBrowser.I18n/Text.xml +++ b/SafeExamBrowser.I18n/Text.xml @@ -222,6 +222,12 @@ Configuration Error + + This computer appears to be a virtual machine. The currently active configuration does not allow SEB to be run in a virtual machine. + + + Virtual Machine Detected + Yes @@ -309,6 +315,9 @@ Terminating user interface + + Validating virtual machine policy + Waiting for Windows explorer to start up diff --git a/SafeExamBrowser.Runtime.UnitTests/Operations/VirtualMachineOperationTests.cs b/SafeExamBrowser.Runtime.UnitTests/Operations/VirtualMachineOperationTests.cs index 1a62c852..2afc2c0a 100644 --- a/SafeExamBrowser.Runtime.UnitTests/Operations/VirtualMachineOperationTests.cs +++ b/SafeExamBrowser.Runtime.UnitTests/Operations/VirtualMachineOperationTests.cs @@ -7,16 +7,135 @@ */ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; +using SafeExamBrowser.Configuration.Contracts; +using SafeExamBrowser.Core.Contracts.OperationModel; +using SafeExamBrowser.Logging.Contracts; +using SafeExamBrowser.Runtime.Operations; +using SafeExamBrowser.Settings; +using SafeExamBrowser.Settings.Security; +using SafeExamBrowser.SystemComponents.Contracts; namespace SafeExamBrowser.Runtime.UnitTests.Operations { [TestClass] public class VirtualMachineOperationTests { - [TestMethod] - public void TODO() + private Mock detector; + private Mock logger; + private SessionContext context; + private VirtualMachineOperation sut; + + [TestInitialize] + public void Initialize() { - Assert.Fail(); + detector = new Mock(); + logger = new Mock(); + context = new SessionContext(); + + context.Next = new SessionConfiguration(); + context.Next.Settings = new AppSettings(); + + sut = new VirtualMachineOperation(detector.Object, logger.Object, context); + } + + [TestMethod] + public void Perform_MustAbortIfVirtualMachineNotAllowed() + { + context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Deny; + detector.Setup(d => d.IsVirtualMachine()).Returns(true); + + var result = sut.Perform(); + + detector.Verify(d => d.IsVirtualMachine(), Times.Once); + Assert.AreEqual(OperationResult.Aborted, result); + } + + [TestMethod] + public void Perform_MustSucceedIfVirtualMachineAllowed() + { + context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Allow; + detector.Setup(d => d.IsVirtualMachine()).Returns(true); + + var result = sut.Perform(); + + detector.Verify(d => d.IsVirtualMachine(), Times.AtMostOnce); + Assert.AreEqual(OperationResult.Success, result); + + context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Allow; + detector.Setup(d => d.IsVirtualMachine()).Returns(false); + + result = sut.Perform(); + + detector.Verify(d => d.IsVirtualMachine(), Times.AtMost(2)); + Assert.AreEqual(OperationResult.Success, result); + } + + [TestMethod] + public void Perform_MustSucceedIfNotAVirtualMachine() + { + context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Deny; + detector.Setup(d => d.IsVirtualMachine()).Returns(false); + + var result = sut.Perform(); + + detector.Verify(d => d.IsVirtualMachine(), Times.Once); + Assert.AreEqual(OperationResult.Success, result); + } + + [TestMethod] + public void Repeat_MustAbortIfVirtualMachineNotAllowed() + { + context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Deny; + detector.Setup(d => d.IsVirtualMachine()).Returns(true); + + var result = sut.Repeat(); + + detector.Verify(d => d.IsVirtualMachine(), Times.Once); + Assert.AreEqual(OperationResult.Aborted, result); + } + + [TestMethod] + public void Repeat_MustSucceedIfVirtualMachineAllowed() + { + context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Allow; + detector.Setup(d => d.IsVirtualMachine()).Returns(true); + + var result = sut.Repeat(); + + detector.Verify(d => d.IsVirtualMachine(), Times.AtMostOnce); + Assert.AreEqual(OperationResult.Success, result); + + context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Allow; + detector.Setup(d => d.IsVirtualMachine()).Returns(false); + + result = sut.Repeat(); + + detector.Verify(d => d.IsVirtualMachine(), Times.AtMost(2)); + Assert.AreEqual(OperationResult.Success, result); + } + + [TestMethod] + public void Repeat_MustSucceedIfNotAVirtualMachine() + { + context.Next.Settings.Security.VirtualMachinePolicy = VirtualMachinePolicy.Deny; + detector.Setup(d => d.IsVirtualMachine()).Returns(false); + + var result = sut.Repeat(); + + detector.Verify(d => d.IsVirtualMachine(), Times.Once); + Assert.AreEqual(OperationResult.Success, result); + } + + [TestMethod] + public void Revert_MustDoNothing() + { + var result = sut.Revert(); + + detector.VerifyNoOtherCalls(); + logger.VerifyNoOtherCalls(); + + Assert.AreEqual(OperationResult.Success, result); } } } diff --git a/SafeExamBrowser.Runtime/CompositionRoot.cs b/SafeExamBrowser.Runtime/CompositionRoot.cs index ee375292..d18e0df4 100644 --- a/SafeExamBrowser.Runtime/CompositionRoot.cs +++ b/SafeExamBrowser.Runtime/CompositionRoot.cs @@ -74,6 +74,7 @@ namespace SafeExamBrowser.Runtime var sessionContext = new SessionContext(); var uiFactory = new UserInterfaceFactory(text); var userInfo = new UserInfo(ModuleLogger(nameof(UserInfo))); + var vmDetector = new VirtualMachineDetector(ModuleLogger(nameof(VirtualMachineDetector)), systemInfo); var bootstrapOperations = new Queue(); var sessionOperations = new Queue(); @@ -83,7 +84,7 @@ namespace SafeExamBrowser.Runtime sessionOperations.Enqueue(new SessionInitializationOperation(configuration, logger, runtimeHost, sessionContext)); sessionOperations.Enqueue(new ConfigurationOperation(args, configuration, new HashAlgorithm(), logger, sessionContext)); - sessionOperations.Enqueue(new VirtualMachineOperation(sessionContext)); + sessionOperations.Enqueue(new VirtualMachineOperation(vmDetector, logger, sessionContext)); sessionOperations.Enqueue(new ServiceOperation(logger, runtimeHost, serviceProxy, sessionContext, THIRTY_SECONDS, userInfo)); sessionOperations.Enqueue(new ClientTerminationOperation(logger, processFactory, proxyFactory, runtimeHost, sessionContext, THIRTY_SECONDS)); sessionOperations.Enqueue(new KioskModeOperation(desktopFactory, explorerShell, logger, processFactory, sessionContext)); diff --git a/SafeExamBrowser.Runtime/Operations/VirtualMachineOperation.cs b/SafeExamBrowser.Runtime/Operations/VirtualMachineOperation.cs index 590d4823..ad66c5df 100644 --- a/SafeExamBrowser.Runtime/Operations/VirtualMachineOperation.cs +++ b/SafeExamBrowser.Runtime/Operations/VirtualMachineOperation.cs @@ -8,34 +8,65 @@ using SafeExamBrowser.Core.Contracts.OperationModel; using SafeExamBrowser.Core.Contracts.OperationModel.Events; +using SafeExamBrowser.I18n.Contracts; +using SafeExamBrowser.Logging.Contracts; +using SafeExamBrowser.Runtime.Operations.Events; +using SafeExamBrowser.Settings.Security; +using SafeExamBrowser.SystemComponents.Contracts; +using SafeExamBrowser.UserInterface.Contracts.MessageBox; namespace SafeExamBrowser.Runtime.Operations { internal class VirtualMachineOperation : SessionOperation { - //private IVirtualMachineDetector detector; + private IVirtualMachineDetector detector; + private ILogger logger; - public VirtualMachineOperation(/*IVirtualMachineDetector detector, */SessionContext context) : base(context) + public VirtualMachineOperation(IVirtualMachineDetector detector, ILogger logger, SessionContext context) : base(context) { - //this.detector = detector; + this.detector = detector; + this.logger = logger; } - public override event ActionRequiredEventHandler ActionRequired { add { } remove { } } - public override event StatusChangedEventHandler StatusChanged { add { } remove { } } + public override event ActionRequiredEventHandler ActionRequired; + public override event StatusChangedEventHandler StatusChanged; public override OperationResult Perform() { - return OperationResult.Success; + return ValidatePolicy(); } public override OperationResult Repeat() { - return OperationResult.Success; + return ValidatePolicy(); } public override OperationResult Revert() { return OperationResult.Success; } + + private OperationResult ValidatePolicy() + { + logger.Info($"Validating virtual machine policy..."); + StatusChanged?.Invoke(TextKey.OperationStatus_ValidateVirtualMachinePolicy); + + if (Context.Next.Settings.Security.VirtualMachinePolicy == VirtualMachinePolicy.Deny && detector.IsVirtualMachine()) + { + var args = new MessageEventArgs + { + Icon = MessageBoxIcon.Error, + Message = TextKey.MessageBox_VirtualMachineNotAllowed, + Title = TextKey.MessageBox_VirtualMachineNotAllowedTitle + }; + + logger.Error("Detected virtual machine while SEB is not allowed to be run in a virtual machine! Aborting startup..."); + ActionRequired?.Invoke(args); + + return OperationResult.Aborted; + } + + return OperationResult.Success; + } } } diff --git a/SafeExamBrowser.SystemComponents.Contracts/IVirtualMachineDetector.cs b/SafeExamBrowser.SystemComponents.Contracts/IVirtualMachineDetector.cs new file mode 100644 index 00000000..a291e651 --- /dev/null +++ b/SafeExamBrowser.SystemComponents.Contracts/IVirtualMachineDetector.cs @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +namespace SafeExamBrowser.SystemComponents.Contracts +{ + /// + /// Provides functionality related to virtual machine detection. + /// + public interface IVirtualMachineDetector + { + /// + /// Indicates whether the computer is in fact a virtual machine. + /// + bool IsVirtualMachine(); + } +} diff --git a/SafeExamBrowser.SystemComponents.Contracts/SafeExamBrowser.SystemComponents.Contracts.csproj b/SafeExamBrowser.SystemComponents.Contracts/SafeExamBrowser.SystemComponents.Contracts.csproj index 876afa1a..d4a6a7ca 100644 --- a/SafeExamBrowser.SystemComponents.Contracts/SafeExamBrowser.SystemComponents.Contracts.csproj +++ b/SafeExamBrowser.SystemComponents.Contracts/SafeExamBrowser.SystemComponents.Contracts.csproj @@ -56,6 +56,7 @@ + diff --git a/SafeExamBrowser.SystemComponents/SafeExamBrowser.SystemComponents.csproj b/SafeExamBrowser.SystemComponents/SafeExamBrowser.SystemComponents.csproj index 9e7216b8..a4414c7f 100644 --- a/SafeExamBrowser.SystemComponents/SafeExamBrowser.SystemComponents.csproj +++ b/SafeExamBrowser.SystemComponents/SafeExamBrowser.SystemComponents.csproj @@ -71,6 +71,7 @@ + diff --git a/SafeExamBrowser.SystemComponents/SystemInfo.cs b/SafeExamBrowser.SystemComponents/SystemInfo.cs index f2bd3c16..77b83c58 100644 --- a/SafeExamBrowser.SystemComponents/SystemInfo.cs +++ b/SafeExamBrowser.SystemComponents/SystemInfo.cs @@ -45,8 +45,8 @@ namespace SafeExamBrowser.SystemComponents private void InitializeMachineInfo() { - var model = string.Empty; - var systemFamily = string.Empty; + var model = default(string); + var systemFamily = default(string); using (var searcher = new ManagementObjectSearcher("Select * from Win32_ComputerSystem")) using (var results = searcher.Get()) diff --git a/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs b/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs new file mode 100644 index 00000000..9c19e020 --- /dev/null +++ b/SafeExamBrowser.SystemComponents/VirtualMachineDetector.cs @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET) + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +using SafeExamBrowser.Logging.Contracts; +using SafeExamBrowser.SystemComponents.Contracts; + +namespace SafeExamBrowser.SystemComponents +{ + public class VirtualMachineDetector : IVirtualMachineDetector + { + private ILogger logger; + private ISystemInfo systemInfo; + + public VirtualMachineDetector(ILogger logger, ISystemInfo systemInfo) + { + this.logger = logger; + this.systemInfo = systemInfo; + } + + public bool IsVirtualMachine() + { + var isVirtualMachine = false; + var manufacturer = systemInfo.Manufacturer.ToLower(); + var model = systemInfo.Model.ToLower(); + + isVirtualMachine |= manufacturer.Contains("microsoft corporation") && !model.Contains("surface"); + isVirtualMachine |= manufacturer.Contains("vmware"); + isVirtualMachine |= manufacturer.Contains("parallels software"); + isVirtualMachine |= model.Contains("virtualbox"); + + logger.Debug($"Computer '{systemInfo.Name}' appears to {(isVirtualMachine ? "" : "not ")}be a virtual machine."); + + return isVirtualMachine; + } + } +}