DouglasDwyer.CasCore
0.1.1
dotnet add package DouglasDwyer.CasCore --version 0.1.1
NuGet\Install-Package DouglasDwyer.CasCore -Version 0.1.1
<PackageReference Include="DouglasDwyer.CasCore" Version="0.1.1" />
paket add DouglasDwyer.CasCore --version 0.1.1
#r "nuget: DouglasDwyer.CasCore, 0.1.1"
// Install DouglasDwyer.CasCore as a Cake Addin #addin nuget:?package=DouglasDwyer.CasCore&version=0.1.1 // Install DouglasDwyer.CasCore as a Cake Tool #tool nuget:?package=DouglasDwyer.CasCore&version=0.1.1
CasCore
Assembly-level sandboxing and Code Access Security for .NET Core
CasCore
allows for securely executing untrusted C# code in an application. When loading an assembly, CasCore
modifies the assembly's bytecode to add security checks. These checks prevent the assembly from violating memory safety or accessing resources without permission. Any assembly loaded with CasCore
is subject to the following restrictions:
- The CIL bytecode of the assembly's methods must be valid and verifiable. Any unverifiable method will throw a
TypeInitializationException
when called. - The assembly may only access a field if it exists within the same assembly, another assembly in the same
AssemblyLoadContext
, or on the assembly'sCasPolicy
whitelist. Any attempt to read/write an inaccessible field fails with aSystem.SecurityException
. - The assembly may only call a method if it exists within the same assembly, another assembly in the same
AssemblyLoadContext
, or on the assembly'sCasPolicy
whitelist. Attempts to call inaccessible methods also fail with an exception. - The assembly may only use reflection APIs to access allowed fields/methods. Attempts with reflection to access invalid fields/methods (according to the same rules as above) also fail with an exception.
- The assembly may only create delegates for allowed methods; attempts to create delegates for invalid methods also fail with an exception.
- The assembly may only use
LambdaExpression.Compile
for expression trees with allowed fields/methods; attempts to create and execute expression trees with invalid methods also fail with an exception.
CasCore
is meant as a replacement for Code Access Security (CAS), which was deprecated in .NET Core. CasCore
was designed primarily for game modding/plugin systems, which may download third-party mods onto a user's computer. CasCore
can prevent mods from performing malicious actions like writing to the filesystem or accessing the network.
Installation
CasCore
can be obtained as a Nuget package. Either run dotnet add package DouglasDwyer.CasCore
via the command line, or download the library from the Visual Studio package manager.
How to use
The full documentation is available here.
Sandboxing assemblies with CasCore
involves two steps: defining a CasPolicy
, then loading assemblies under that policy.
The CasPolicy
defines a whitelist of fields and methods that sandboxed assemblies may access. Any external member accesses not explicitly on the policy whitelist will fail with a System.SecurityException
. Policies are created using the CasPolicyBuilder
class:
var policy = new CasPolicyBuilder() // Create a new, empty whitelist.
.WithDefaultSandbox() // Add all system types that are on the default whitelist
.Allow(new TypeBinding(typeof(FullyAccessibleClass), Accessibility.Private)) // This class will be fully accessible
.Allow(new TypeBinding(typeof(InheritableAccessibleClass), Accessibility.Protected)) // Public/protected members only
.Allow(new TypeBinding(typeof(PublicAccessibleClass), Accessibility.Public)) // Public members only
.Allow(new TypeBinding(typeof(PartiallyAccessibleClass), Accessibility.None) // Only the following members
.WithConstructor([], Accessibility.Public)
.WithField("AllowedStaticField", Accessibility.Public)
.WithField("AllowedField", Accessibility.Public)
.WithMethod("InterfaceMethod", Accessibility.Public))
.Allow(new AssemblyBinding(Assembly.Load("SomeAssembly"), Accessibility.Protected)) // All public/protected members of assembly
.Build(); // Generate the final policy
After a policy has been defined, it can be used with a CasAssemblyLoader
. Any assemblies created using the loader will be subject to the policy:
var loadContext = new CasAssemblyLoader(policy);
var assembly = loadContext.LoadFromAssemblyPath("Newtonsoft.Json.dll");
// The types in assembly can only access external code if it is whitelisted OR if that code was loaded with the same loader
Default sandbox policy
CasPolicyBuilder
comes with extension methods (most notably WithDefaultSandbox
) that add members from the C# standard library. These methods are meant to whitelist as much of the C# standard library as possible, excluding any methods that:
- Cause undefined behavior (such as the
Unsafe
class or intrinsics) - Access the filesystem
- Access the network
- Access other OS-specific resources (such as processes or pipes)
- Allow for loading other code without verification (such as
System.Reflection.Emit
)
The WithDefaultSandbox
method should provide a sensible default whitelist that ensures any loaded assemblies cannot gain access to the host system. Testing reveals that the netstandard
version of Newtonsoft.Json
is able to run with the default sandbox policy, so it should be fairly comprehensive.
How it works
This library has three main components:
- CIL verification - before a sandboxed assembly is loaded, it is modified using Mono.Cecil. Some "guard" CIL instructions are inserted at the beginning of each method in the assembly. When such a method is called for the first time, the guard instructions cause a fork of Microsoft's ILVerify to run on the method bytecode. The validity of the bytecode is checked and an error is raised if the bytecode does not conform to the CLR ECMA standard.
- Insertion of runtime checks - next, the bytecode of each method is scanned with Cecil. A runtime check is inserted before any external field access or method call (this includes virtual method calls and the creation of delegates). The check causes an exception to be thrown if the assembly does not have permission to access the member. The runtime checks are designed so that the JIT will compile them out if the member is both accessible and non-virtual.
- Calls to shims - finally, calls to reflection-related APIs (such as
System.Activator.CreateInstance
orSystem.Reflection.MethodInfo.Invoke
) are replaced with calls to shims. These shims perform a runtime check to ensure that the reflected member is accessible. If not, an exception is thrown.
Testing
Included in this repository are a set of unit tests to demonstrate the system's functionality. The unit tests show that things like causing memory unsafety or accessing the file system are impossible under the default sandbox policy. Contributions and pull requests to test/address any remaining security vulnerabilities are appreciated.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net8.0
- DouglasDwyer.JitIlVerification (>= 0.1.3)
- Mono.Cecil (>= 0.11.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.