Expose memory allocation sandboxing through VMContext.

This commit is contained in:
Florian Nücke
2020-12-19 03:23:48 +01:00
parent e035aa6971
commit 32d3d3ada3
6 changed files with 92 additions and 20 deletions

View File

@@ -0,0 +1,14 @@
package li.cil.oc2.api.bus.device.vm;
/**
* A memory allocator used to ensure sandbox limits when loading devices.
*/
public interface MemoryAllocator {
/**
* Tries to reserve the specified amount of memory, in bytes.
*
* @param size the amount of memory to reserve, in bytes.
* @return {@code true} when the memory was claimed successfully; {@code false} otherwise.
*/
boolean claimMemory(int size);
}

View File

@@ -29,7 +29,8 @@ public interface VMContext {
* made available through this instance will result in an exception.
* <p>
* Interrupts raised will automatically be lowered when the {@link VMDevice} that
* raised them is unloaded, e.g. because it is removed from the {@link DeviceBus}.
* raised them is unloaded, e.g. because it is removed from the {@link DeviceBus}
* or the VM stopped.
*
* @return the interrupt controller of the virtual machine.
*/
@@ -42,7 +43,8 @@ public interface VMContext {
* Trying to add devices after that method has returned will result in an exception.
* <p>
* Added devices will be automatically removed when the {@link VMDevice} that added it
* is unloaded, e.g. because it has been removed from the {@link DeviceBus}.
* is unloaded, e.g. because it has been removed from the {@link DeviceBus} or the VM
* stopped.
*
* @return the memory range allocator.
*/
@@ -60,4 +62,23 @@ public interface VMContext {
* @return the interrupt allocator.
*/
InterruptAllocator getInterruptAllocator();
/**
* Allows reserving fixed amounts of memory respecting sandbox constraints.
* <p>
* It is strongly advised to use this allocator to make known large memory
* uses, e.g. when allocating large blobs for RAM or block devices. This
* allows respecting the built-in limits for overall memory usage of
* running VMs.
* <p>
* Devices failing to reserve the memory they would use should fail their
* {@link VMDevice#load(VMContext)}.
* <p>
* Memory will automatically be released when the {@link VMDevice} that claimed
* it is unloaded, e.g. because it is removed from the {@link DeviceBus} or the
* VM stopped.
*
* @return the memory allocator.
*/
MemoryAllocator getMemoryAllocator();
}

View File

@@ -5,7 +5,6 @@ import li.cil.oc2.common.bus.device.provider.util.AbstractObjectProxy;
import li.cil.oc2.common.serialization.BlobStorage;
import li.cil.oc2.common.serialization.NBTSerialization;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.vm.Allocator;
import li.cil.sedna.api.device.BlockDevice;
import li.cil.sedna.device.virtio.VirtIOBlockDevice;
import net.minecraft.item.ItemStack;
@@ -28,7 +27,6 @@ public abstract class AbstractHardDiskDriveDevice<T extends BlockDevice> extends
///////////////////////////////////////////////////////////////
private final UUID allocHandle = Allocator.createHandle();
private BlobStorage.JobHandle jobHandle;
private T data;
private VirtIOBlockDevice device;
@@ -56,12 +54,10 @@ public abstract class AbstractHardDiskDriveDevice<T extends BlockDevice> extends
}
if (!claimAddress(context)) {
Allocator.freeMemory(allocHandle);
return VMDeviceLoadResult.fail();
}
if (!claimInterrupt(context)) {
Allocator.freeMemory(allocHandle);
return VMDeviceLoadResult.fail();
}
@@ -152,7 +148,7 @@ public abstract class AbstractHardDiskDriveDevice<T extends BlockDevice> extends
///////////////////////////////////////////////////////////////
private boolean allocateDevice(final VMContext context) {
if (!Allocator.claimMemory(allocHandle, getSize())) {
if (!context.getMemoryAllocator().claimMemory(getSize())) {
return false;
}
@@ -231,12 +227,12 @@ public abstract class AbstractHardDiskDriveDevice<T extends BlockDevice> extends
}
}
Allocator.freeMemory(allocHandle);
data = null;
device = null;
deviceNbt = null;
address = null;
interrupt = null;
jobHandle = null;
}
}

View File

@@ -9,7 +9,6 @@ import li.cil.oc2.common.bus.device.provider.util.AbstractItemDeviceProvider;
import li.cil.oc2.common.bus.device.provider.util.AbstractObjectProxy;
import li.cil.oc2.common.serialization.BlobStorage;
import li.cil.oc2.common.util.NBTTagIds;
import li.cil.oc2.common.vm.Allocator;
import li.cil.sedna.api.device.PhysicalMemory;
import li.cil.sedna.device.memory.Memory;
import li.cil.sedna.memory.PhysicalMemoryInputStream;
@@ -46,7 +45,6 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
///////////////////////////////////////////////////////////////
private final UUID allocHandle = Allocator.createHandle();
private BlobStorage.JobHandle jobHandle;
private PhysicalMemory device;
@@ -63,7 +61,7 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
@Override
public VMDeviceLoadResult load(final VMContext context) {
if (!allocateDevice()) {
if (!allocateDevice(context)) {
return VMDeviceLoadResult.fail();
}
@@ -122,8 +120,8 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
///////////////////////////////////////////////////////////////
private boolean allocateDevice() {
if (!Allocator.claimMemory(allocHandle, RAM_SIZE)) {
private boolean allocateDevice(final VMContext context) {
if (!context.getMemoryAllocator().claimMemory(RAM_SIZE)) {
return false;
}
@@ -141,7 +139,6 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
}
if (!claimedAddress.isPresent()) {
Allocator.freeMemory(allocHandle);
return false;
}
@@ -164,17 +161,13 @@ public final class MemoryItemDeviceProvider extends AbstractItemDeviceProvider {
}
private void unload() {
// Finish saves on unload to ensure future loads will read correct data.
awaitStorageOperation();
Allocator.freeMemory(allocHandle);
device = null;
// RAM is volatile, so free up our persisted blob when device is unloaded.
BlobStorage.freeHandle(blobHandle);
blobHandle = null;
device = null;
address = null;
jobHandle = null;
}
}
}

View File

@@ -0,0 +1,38 @@
package li.cil.oc2.common.vm;
import li.cil.oc2.api.bus.device.vm.MemoryAllocator;
import java.util.ArrayList;
import java.util.UUID;
public final class ManagedMemoryAllocator implements MemoryAllocator {
private final ArrayList<UUID> claimedMemory = new ArrayList<>();
private boolean isFrozen;
public void freeze() {
isFrozen = true;
}
public void invalidate() {
for (final UUID handle : claimedMemory) {
Allocator.freeMemory(handle);
}
claimedMemory.clear();
}
@Override
public boolean claimMemory(final int size) {
if (isFrozen) {
throw new IllegalStateException();
}
final UUID handle = Allocator.createHandle();
if (!Allocator.claimMemory(handle, size)) {
return false;
}
claimedMemory.add(handle);
return true;
}
}

View File

@@ -1,6 +1,7 @@
package li.cil.oc2.common.vm;
import li.cil.oc2.api.bus.device.vm.InterruptAllocator;
import li.cil.oc2.api.bus.device.vm.MemoryAllocator;
import li.cil.oc2.api.bus.device.vm.MemoryRangeAllocator;
import li.cil.oc2.api.bus.device.vm.VMContext;
import li.cil.sedna.api.Board;
@@ -14,6 +15,7 @@ public final class ManagedVMContext implements VMContext {
private final ManagedInterruptController interruptController;
private final ManagedMemoryRangeAllocator memoryRangeAllocator;
private final ManagedInterruptAllocator interruptAllocator;
private final ManagedMemoryAllocator memoryAllocator;
///////////////////////////////////////////////////////////////////
@@ -22,17 +24,20 @@ public final class ManagedVMContext implements VMContext {
this.interruptAllocator = new ManagedInterruptAllocator(claimedInterrupts, reservedInterrupts, board.getInterruptCount());
this.memoryMap = new ManagedMemoryMap(board.getMemoryMap());
this.interruptController = new ManagedInterruptController(board.getInterruptController(), interruptAllocator);
this.memoryAllocator = new ManagedMemoryAllocator();
}
public void freeze() {
memoryRangeAllocator.freeze();
interruptAllocator.freeze();
memoryAllocator.freeze();
}
public void invalidate() {
memoryRangeAllocator.invalidate();
interruptAllocator.invalidate();
interruptController.invalidate();
memoryAllocator.invalidate();
}
@Override
@@ -54,4 +59,9 @@ public final class ManagedVMContext implements VMContext {
public InterruptAllocator getInterruptAllocator() {
return interruptAllocator;
}
@Override
public MemoryAllocator getMemoryAllocator() {
return memoryAllocator;
}
}