From 32d3d3ada33d0754f8ced5a561f493eb166f5645 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sat, 19 Dec 2020 03:23:48 +0100 Subject: [PATCH] Expose memory allocation sandboxing through VMContext. --- .../api/bus/device/vm/MemoryAllocator.java | 14 +++++++ .../cil/oc2/api/bus/device/vm/VMContext.java | 25 +++++++++++- .../device/AbstractHardDiskDriveDevice.java | 8 +--- .../provider/MemoryItemDeviceProvider.java | 17 +++------ .../oc2/common/vm/ManagedMemoryAllocator.java | 38 +++++++++++++++++++ .../cil/oc2/common/vm/ManagedVMContext.java | 10 +++++ 6 files changed, 92 insertions(+), 20 deletions(-) create mode 100644 src/main/java/li/cil/oc2/api/bus/device/vm/MemoryAllocator.java create mode 100644 src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryAllocator.java b/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryAllocator.java new file mode 100644 index 00000000..c09543e3 --- /dev/null +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/MemoryAllocator.java @@ -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); +} diff --git a/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java b/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java index d990041a..35545825 100644 --- a/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java +++ b/src/main/java/li/cil/oc2/api/bus/device/vm/VMContext.java @@ -29,7 +29,8 @@ public interface VMContext { * made available through this instance will result in an exception. *

* 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. *

* 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. + *

+ * 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. + *

+ * Devices failing to reserve the memory they would use should fail their + * {@link VMDevice#load(VMContext)}. + *

+ * 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(); } diff --git a/src/main/java/li/cil/oc2/common/bus/device/AbstractHardDiskDriveDevice.java b/src/main/java/li/cil/oc2/common/bus/device/AbstractHardDiskDriveDevice.java index 6befa750..53f69961 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/AbstractHardDiskDriveDevice.java +++ b/src/main/java/li/cil/oc2/common/bus/device/AbstractHardDiskDriveDevice.java @@ -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 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 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 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 extends } } - Allocator.freeMemory(allocHandle); data = null; device = null; deviceNbt = null; address = null; interrupt = null; + jobHandle = null; } } diff --git a/src/main/java/li/cil/oc2/common/bus/device/provider/MemoryItemDeviceProvider.java b/src/main/java/li/cil/oc2/common/bus/device/provider/MemoryItemDeviceProvider.java index 06637e7c..a235cdf8 100644 --- a/src/main/java/li/cil/oc2/common/bus/device/provider/MemoryItemDeviceProvider.java +++ b/src/main/java/li/cil/oc2/common/bus/device/provider/MemoryItemDeviceProvider.java @@ -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; } } } diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java new file mode 100644 index 00000000..d7c7ae06 --- /dev/null +++ b/src/main/java/li/cil/oc2/common/vm/ManagedMemoryAllocator.java @@ -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 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; + } +} diff --git a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java b/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java index 07cbb145..a23dc072 100644 --- a/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java +++ b/src/main/java/li/cil/oc2/common/vm/ManagedVMContext.java @@ -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; + } }