WIP checkpoint: Research and design GDScript sandboxing approach
This commit is contained in:
+95
-80
@@ -2,91 +2,83 @@ extends Node
|
|||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
print("=== SANDBOX PROBE 3: Deep Sandboxing Mechanisms ===")
|
print("=== SANDBOX PROBE 3: Deep Sandboxing Mechanisms ===")
|
||||||
probe_script_restriction_apis()
|
probe_global_scope_restriction()
|
||||||
probe_global_scope()
|
probe_classdb_restriction()
|
||||||
probe_classdb_restrictions()
|
|
||||||
probe_engine_singletons()
|
probe_engine_singletons()
|
||||||
probe_network_restrictions()
|
probe_script_validation()
|
||||||
probe_threading()
|
probe_expression_engine()
|
||||||
probe_expression_eval()
|
probe_worker_threads()
|
||||||
probe_resource_loading()
|
probe_os_restriction_flags()
|
||||||
print("=== SANDBOX PROBE 3 END ===")
|
print("=== SANDBOX PROBE 3 END ===")
|
||||||
get_tree().quit()
|
get_tree().quit()
|
||||||
|
|
||||||
func probe_script_restriction_apis():
|
func probe_global_scope_restriction():
|
||||||
print("--- Script restriction APIs ---")
|
print("--- Global scope restriction ---")
|
||||||
# Does GDScript expose any way to restrict globals?
|
# Can we enumerate what's in the global scope?
|
||||||
var gd = GDScript.new()
|
|
||||||
print(" GDScript methods: ", gd.get_method_list().map(func(m): return m.name))
|
|
||||||
# Check for any 'restrict' or 'allow' methods on the language
|
|
||||||
var lang = GDScriptLanguage.new()
|
|
||||||
print(" GDScriptLanguage methods: ", lang.get_method_list().map(func(m): return m.name))
|
|
||||||
# Check ScriptServer via Engine
|
|
||||||
print(" Engine.get_singleton_list contains ScriptServer: ", "ScriptServer" in Engine.get_singleton_list())
|
|
||||||
|
|
||||||
func probe_global_scope():
|
|
||||||
print("--- Global scope ---")
|
|
||||||
# What globals are available to a script?
|
|
||||||
# Test if we can enumerate global classes
|
|
||||||
var globals = []
|
var globals = []
|
||||||
for g in GlobalScope.get_singleton_list():
|
for g in GDScript.get_global_class_list():
|
||||||
globals.append(g)
|
globals.append(g)
|
||||||
print(" GlobalScope singletons: ", globals)
|
print(" GDScript.get_global_class_list() count: ", globals.size())
|
||||||
# Can we access ClassDB to enumerate classes?
|
# Can we check if a global is available before using it?
|
||||||
|
print(" GDScript has 'get_global_class_list': ", GDScript.has_method("get_global_class_list"))
|
||||||
|
# Is there any way to restrict which globals a script sees?
|
||||||
|
print(" GDScript has 'set_global_scope': ", GDScript.has_method("set_global_scope"))
|
||||||
|
print(" GDScript has 'restrict_globals': ", GDScript.has_method("restrict_globals"))
|
||||||
|
# Check the GDScript class methods
|
||||||
|
var methods = GDScript.get_method_list()
|
||||||
|
var method_names = []
|
||||||
|
for m in methods:
|
||||||
|
method_names.append(m.name)
|
||||||
|
print(" GDScript methods: ", method_names)
|
||||||
|
|
||||||
|
func probe_classdb_restriction():
|
||||||
|
print("--- ClassDB restriction ---")
|
||||||
|
# ClassDB is the registry of all engine classes. Can we restrict it?
|
||||||
|
print(" ClassDB has 'get_class_list': ", ClassDB.has_method("get_class_list"))
|
||||||
|
print(" ClassDB has 'class_exists': ", ClassDB.has_method("class_exists"))
|
||||||
|
# Can we check if a specific class is available?
|
||||||
print(" ClassDB.class_exists('OS'): ", ClassDB.class_exists("OS"))
|
print(" ClassDB.class_exists('OS'): ", ClassDB.class_exists("OS"))
|
||||||
print(" ClassDB.class_exists('FileAccess'): ", ClassDB.class_exists("FileAccess"))
|
print(" ClassDB.class_exists('FileAccess'): ", ClassDB.class_exists("FileAccess"))
|
||||||
print(" ClassDB.class_exists('TCPServer'): ", ClassDB.class_exists("TCPServer"))
|
print(" ClassDB.class_exists('TCPServer'): ", ClassDB.class_exists("TCPServer"))
|
||||||
|
# Is there a way to hide classes from scripts?
|
||||||
func probe_classdb_restrictions():
|
print(" ClassDB has 'set_class_enabled': ", ClassDB.has_method("set_class_enabled"))
|
||||||
print("--- ClassDB restrictions ---")
|
print(" ClassDB has 'disable_class': ", ClassDB.has_method("disable_class"))
|
||||||
# Can we instantiate arbitrary classes?
|
|
||||||
var os = ClassDB.instantiate("OS")
|
|
||||||
print(" ClassDB.instantiate('OS') = ", os)
|
|
||||||
# Can we call static methods via ClassDB?
|
|
||||||
print(" ClassDB has method 'class_get_method_list': ", ClassDB.has_method("class_get_method_list"))
|
|
||||||
# Can we access the full class list?
|
|
||||||
var class_list = ClassDB.get_class_list()
|
|
||||||
print(" ClassDB.get_class_list() count: ", class_list.size())
|
|
||||||
print(" First 10 classes: ", class_list.slice(0, 10))
|
|
||||||
|
|
||||||
func probe_engine_singletons():
|
func probe_engine_singletons():
|
||||||
print("--- Engine singletons ---")
|
print("--- Engine singletons ---")
|
||||||
# What singletons are available?
|
# Engine singletons are the global objects like OS, Engine, etc.
|
||||||
var singletons = Engine.get_singleton_list()
|
# Can we enumerate them?
|
||||||
print(" Engine singletons: ", singletons)
|
var singletons = Engine.get_singletons()
|
||||||
# Can we get a singleton by name?
|
print(" Engine.get_singletons() count: ", singletons.size())
|
||||||
for s in ["OS", "Engine", "ProjectSettings", "ResourceLoader", "ClassDB", "Input", "DisplayServer"]:
|
for s in singletons:
|
||||||
print(" Engine.has_singleton('", s, "'): ", Engine.has_singleton(s))
|
print(" singleton: ", s)
|
||||||
|
# Can we remove a singleton?
|
||||||
|
print(" Engine has 'remove_singleton': ", Engine.has_method("remove_singleton"))
|
||||||
|
print(" Engine has 'add_singleton': ", Engine.has_method("add_singleton"))
|
||||||
|
|
||||||
func probe_network_restrictions():
|
func probe_script_validation():
|
||||||
print("--- Network restrictions ---")
|
print("--- Script validation ---")
|
||||||
# Can we make outbound HTTP requests?
|
# Can we validate a script before running it?
|
||||||
var http = HTTPRequest.new()
|
var gd = GDScript.new()
|
||||||
print(" HTTPRequest.new() = ", http)
|
gd.source_code = "extends RefCounted\nfunc evil():\n\treturn OS.get_environment('HOME')\n"
|
||||||
# Can we create a WebSocket client?
|
var err = gd.reload()
|
||||||
var ws = WebSocketPeer.new()
|
print(" GDScript.reload() err=", err)
|
||||||
print(" WebSocketPeer.new() = ", ws)
|
# Can we get the list of identifiers used in a script?
|
||||||
# Can we access the network via PacketPeer?
|
print(" GDScript has 'get_script_method_list': ", gd.has_method("get_script_method_list"))
|
||||||
var pp = PacketPeerUDP.new()
|
# Can we get the source code back?
|
||||||
print(" PacketPeerUDP.new() = ", pp)
|
print(" GDScript has 'get_source_code': ", gd.has_method("get_source_code"))
|
||||||
|
# Can we check what identifiers a script references?
|
||||||
|
print(" GDScript has 'get_script_constant_map': ", gd.has_method("get_script_constant_map"))
|
||||||
|
# Check if there's a way to get the AST
|
||||||
|
print(" GDScript has 'get_ast': ", gd.has_method("get_ast"))
|
||||||
|
print(" GDScript has 'get_parser': ", gd.has_method("get_parser"))
|
||||||
|
|
||||||
func probe_threading():
|
func probe_expression_engine():
|
||||||
print("--- Threading ---")
|
print("--- Expression engine ---")
|
||||||
# Can we spawn threads?
|
# Godot has an Expression class for evaluating expressions safely
|
||||||
var thread = Thread.new()
|
|
||||||
print(" Thread.new() = ", thread)
|
|
||||||
# Can we use mutexes / semaphores?
|
|
||||||
var mutex = Mutex.new()
|
|
||||||
print(" Mutex.new() = ", mutex)
|
|
||||||
var sem = Semaphore.new()
|
|
||||||
print(" Semaphore.new() = ", sem)
|
|
||||||
|
|
||||||
func probe_expression_eval():
|
|
||||||
print("--- Expression evaluation ---")
|
|
||||||
# Godot's Expression class - can it evaluate arbitrary code?
|
|
||||||
var expr = Expression.new()
|
var expr = Expression.new()
|
||||||
var err = expr.parse("1 + 2")
|
var err = expr.parse("1 + 2")
|
||||||
print(" Expression.parse('1+2') err=", err)
|
print(" Expression.parse('1 + 2') err=", err)
|
||||||
if err == OK:
|
if err == OK:
|
||||||
var result = expr.execute()
|
var result = expr.execute()
|
||||||
print(" Expression.execute() = ", result)
|
print(" Expression.execute() = ", result)
|
||||||
@@ -97,15 +89,38 @@ func probe_expression_eval():
|
|||||||
if err == OK:
|
if err == OK:
|
||||||
var result2 = expr2.execute()
|
var result2 = expr2.execute()
|
||||||
print(" Expression.execute() = ", result2)
|
print(" Expression.execute() = ", result2)
|
||||||
|
# Expression with base_instance
|
||||||
|
var expr3 = Expression.new()
|
||||||
|
err = expr3.parse("get_environment('HOME')", ["OS"])
|
||||||
|
print(" Expression.parse with base_instance err=", err)
|
||||||
|
if err == OK:
|
||||||
|
var result3 = expr3.execute([], OS)
|
||||||
|
print(" Expression.execute with OS base = ", result3)
|
||||||
|
|
||||||
func probe_resource_loading():
|
func probe_worker_threads():
|
||||||
print("--- Resource loading ---")
|
print("--- Worker threads ---")
|
||||||
# Can we load arbitrary resources?
|
# Can we run scripts in worker threads?
|
||||||
var res = load("res://test_script.gd")
|
print(" Thread has 'new': ", Thread.has_method("new"))
|
||||||
print(" load('res://test_script.gd') = ", res)
|
# WorkerThreadPool
|
||||||
# Can we load from absolute paths?
|
print(" WorkerThreadPool has 'add_task': ", WorkerThreadPool.has_method("add_task"))
|
||||||
var res2 = load("/etc/hostname")
|
# Can we isolate a script in a thread?
|
||||||
print(" load('/etc/hostname') = ", res2)
|
print(" Thread available: ", Thread != null)
|
||||||
# Can we use ResourceLoader directly?
|
|
||||||
var rl = ResourceLoader
|
func probe_os_restriction_flags():
|
||||||
print(" ResourceLoader.load('res://test_script.gd') = ", rl.load("res://test_script.gd"))
|
print("--- OS restriction flags ---")
|
||||||
|
# Check if there are any OS-level sandbox flags
|
||||||
|
print(" OS has 'get_environment': ", OS.has_method("get_environment"))
|
||||||
|
print(" OS has 'execute': ", OS.has_method("execute"))
|
||||||
|
print(" OS has 'create_process': ", OS.has_method("create_process"))
|
||||||
|
# Check project settings for sandbox-related options
|
||||||
|
var settings = ProjectSettings.get_property_list()
|
||||||
|
var relevant = []
|
||||||
|
for s in settings:
|
||||||
|
var name = String(s.name)
|
||||||
|
if "sandbox" in name.to_lower() or "restrict" in name.to_lower() or "security" in name.to_lower():
|
||||||
|
relevant.append(name)
|
||||||
|
print(" Sandbox/security project settings: ", relevant)
|
||||||
|
# Check if there's a way to disable specific features
|
||||||
|
print(" ProjectSettings has 'application/run/disable_os': ", ProjectSettings.has_setting("application/run/disable_os"))
|
||||||
|
print(" ProjectSettings has 'application/run/disable_file_access': ", ProjectSettings.has_setting("application/run/disable_file_access"))
|
||||||
|
print(" ProjectSettings has 'application/run/disable_network': ", ProjectSettings.has_setting("application/run/disable_network"))
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
[gd_scene load_steps=2 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://probe3.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="Probe3" type="Node"]
|
||||||
|
script = ExtResource("1")
|
||||||
Reference in New Issue
Block a user