WIP checkpoint: Research and design GDScript sandboxing approach
This commit is contained in:
+65
-60
@@ -1,78 +1,83 @@
|
|||||||
extends Node
|
extends Node
|
||||||
|
|
||||||
func _ready():
|
func _ready():
|
||||||
print("=== SANDBOX PROBE 2: ScriptServer & Restrictions ===")
|
print("=== SANDBOX PROBE 2: Sandboxing Mechanisms ===")
|
||||||
probe_scriptserver()
|
probe_scriptserver()
|
||||||
probe_script_restrictions()
|
probe_script_restrictions()
|
||||||
|
probe_subviewport_isolation()
|
||||||
probe_engine_restrictions()
|
probe_engine_restrictions()
|
||||||
probe_global_scope()
|
probe_allowlist_approach()
|
||||||
probe_classdb()
|
|
||||||
probe_script_extension()
|
|
||||||
print("=== SANDBOX PROBE 2 END ===")
|
print("=== SANDBOX PROBE 2 END ===")
|
||||||
get_tree().quit()
|
get_tree().quit()
|
||||||
|
|
||||||
func probe_scriptserver():
|
func probe_scriptserver():
|
||||||
print("--- ScriptServer ---")
|
print("--- ScriptServer ---")
|
||||||
# Is ScriptServer available? It's a static class in Godot 4.
|
# Does ScriptServer exist and what can it do?
|
||||||
print("ScriptServer exists: ", ClassDB.class_exists("ScriptServer"))
|
print("ScriptServer has_method('get_language_count'): ", ScriptServer.has_method("get_language_count"))
|
||||||
if ClassDB.class_exists("ScriptServer"):
|
print("ScriptServer has_method('get_language'): ", ScriptServer.has_method("get_language"))
|
||||||
# What methods does it expose?
|
# Can we enumerate registered script languages?
|
||||||
var methods = ClassDB.class_get_method_list("ScriptServer")
|
if ScriptServer.has_method("get_language_count"):
|
||||||
for m in methods:
|
var count = ScriptServer.get_language_count()
|
||||||
print(" ScriptServer method: ", m.name, " args=", m.args.size())
|
print(" language count: ", count)
|
||||||
|
for i in range(count):
|
||||||
|
var lang = ScriptServer.get_language(i)
|
||||||
|
print(" language[", i, "] = ", lang)
|
||||||
|
|
||||||
func probe_script_restrictions():
|
func probe_script_restrictions():
|
||||||
print("--- Script restrictions ---")
|
print("--- Script restrictions ---")
|
||||||
# Can we restrict script loading via ProjectSettings?
|
# Can we restrict which classes/globals a script can access?
|
||||||
var keys = [
|
# Test: does GDScript have any built-in allowlist mechanism?
|
||||||
"debug/gdscript/warnings/",
|
var gd = GDScript.new()
|
||||||
"debug/gdscript/",
|
gd.source_code = "extends RefCounted\nfunc evil():\n\treturn OS.get_environment('HOME')\n"
|
||||||
"application/run/",
|
var err = gd.reload()
|
||||||
]
|
print(" GDScript with OS access reload err=", err)
|
||||||
for k in keys:
|
if err == OK:
|
||||||
var settings = ProjectSettings.get_setting(k)
|
var inst = gd.new()
|
||||||
print(" ProjectSettings['", k, "'] = ", settings)
|
print(" evil() result: ", inst.call("evil"))
|
||||||
|
# Can we block specific global classes?
|
||||||
|
print(" GDScript has method to restrict globals: ", gd.has_method("set_global_scope"))
|
||||||
|
|
||||||
|
func probe_subviewport_isolation():
|
||||||
|
print("--- SubViewport isolation ---")
|
||||||
|
# SubViewports isolate rendering, but do they isolate script execution?
|
||||||
|
var vp = SubViewport.new()
|
||||||
|
print(" SubViewport.new() = ", vp)
|
||||||
|
# SubViewports share the same process/engine - scripts still have full access
|
||||||
|
print(" SubViewport does NOT isolate script execution (same process)")
|
||||||
|
vp.free()
|
||||||
|
|
||||||
func probe_engine_restrictions():
|
func probe_engine_restrictions():
|
||||||
print("--- Engine restrictions ---")
|
print("--- Engine restrictions ---")
|
||||||
# Check if there's any sandbox/restriction setting
|
# Is there a way to disable OS/File/Network access globally?
|
||||||
var all_settings = ProjectSettings.get_property_list()
|
print(" Engine.has_singleton('OS'): ", Engine.has_singleton("OS"))
|
||||||
var sandbox_like = []
|
print(" Engine.has_singleton('FileAccess'): ", Engine.has_singleton("FileAccess"))
|
||||||
for s in all_settings:
|
# Check if there's a '--disable-*' flag or project setting
|
||||||
var name = String(s.name)
|
print(" ProjectSettings has 'application/run/disable_*': ", ProjectSettings.has_setting("application/run/disable_os"))
|
||||||
if "sandbox" in name.to_lower() or "restrict" in name.to_lower() or "security" in name.to_lower():
|
# Check for any sandbox-related project settings
|
||||||
sandbox_like.append(name)
|
var settings = ProjectSettings.get_property_list()
|
||||||
print(" Sandbox/restrict/security settings found: ", sandbox_like)
|
var sandbox_settings = []
|
||||||
|
for s in settings:
|
||||||
|
if "sandbox" in String(s.name).to_lower() or "restrict" in String(s.name).to_lower():
|
||||||
|
sandbox_settings.append(s.name)
|
||||||
|
print(" Sandbox-related project settings: ", sandbox_settings)
|
||||||
|
|
||||||
func probe_global_scope():
|
func probe_allowlist_approach():
|
||||||
print("--- Global scope ---")
|
print("--- Allowlist approach ---")
|
||||||
# What global classes/functions are available to scripts?
|
# The allowlist approach: parse GDScript source and reject disallowed patterns
|
||||||
# Check if we can enumerate global scope
|
# Test: can we detect dangerous API calls via string analysis?
|
||||||
var global_classes = []
|
var dangerous_patterns = [
|
||||||
if ClassDB.class_exists("GlobalScope"):
|
"OS.execute", "OS.create_process", "OS.get_environment",
|
||||||
var methods = ClassDB.class_get_method_list("GlobalScope")
|
"FileAccess.open", "DirAccess.open", "TCPServer", "HTTPClient",
|
||||||
for m in methods:
|
"load(", "preload(", "ClassDB", "Engine.get_main_loop",
|
||||||
global_classes.append(m.name)
|
"get_tree().quit", "ProjectSettings"
|
||||||
print(" GlobalScope methods count: ", global_classes.size())
|
]
|
||||||
# Key dangerous globals
|
var test_script = """
|
||||||
var dangerous = ["OS", "FileAccess", "DirAccess", "TCPServer", "HTTPClient", "ProjectSettings", "Engine", "ResourceLoader", "ClassDB", "Expression", "JavaScriptBridge"]
|
extends Node
|
||||||
for d in dangerous:
|
func _ready():
|
||||||
print(" Class '", d, "' exists: ", ClassDB.class_exists(d))
|
var f = FileAccess.open("/etc/passwd", FileAccess.READ)
|
||||||
|
OS.execute("rm", ["-rf", "/"])
|
||||||
func probe_classdb():
|
"""
|
||||||
print("--- ClassDB ---")
|
print(" Test script contains dangerous patterns:")
|
||||||
# Can scripts access ClassDB to instantiate arbitrary classes?
|
for p in dangerous_patterns:
|
||||||
print(" ClassDB.class_exists('Node') = ", ClassDB.class_exists("Node"))
|
if p in test_script:
|
||||||
# Can we get all classes?
|
print(" DETECTED: ", p)
|
||||||
var all_classes = ClassDB.get_class_list()
|
|
||||||
print(" ClassDB.get_class_list().size() = ", all_classes.size())
|
|
||||||
|
|
||||||
func probe_script_extension():
|
|
||||||
print("--- Script extension / custom interpreter ---")
|
|
||||||
# Check if ScriptExtension exists (for custom script languages)
|
|
||||||
print(" ScriptExtension exists: ", ClassDB.class_exists("ScriptExtension"))
|
|
||||||
# Check GDScript internals
|
|
||||||
print(" GDScript class exists: ", ClassDB.class_exists("GDScript"))
|
|
||||||
var gd_methods = ClassDB.class_get_method_list("GDScript")
|
|
||||||
for m in gd_methods:
|
|
||||||
print(" GDScript method: ", m.name)
|
|
||||||
|
|||||||
Reference in New Issue
Block a user