diff --git a/CHANGES.md b/CHANGES.md index 88e4ee4..8590eb4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,5 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.0] - 2023-11-19 +### Breaking change +- Added a scan for any kind of resource that is outside of res://. If resources now contain any reference to another resource that is outside of res:// they will not be loaded. This prevents injecting scripts by putting script files next to the resource. + ## [0.0.1] - 2023-10-12 - Initial release. diff --git a/addons/safe_resource_loader/safe_resource_loader.gd b/addons/safe_resource_loader/safe_resource_loader.gd index 7e0da24..22d39ff 100644 --- a/addons/safe_resource_loader/safe_resource_loader.gd +++ b/addons/safe_resource_loader/safe_resource_loader.gd @@ -40,7 +40,26 @@ static func load(path:String, type_hint:String = "", \ if regex.search(file_as_text) != null: push_warning("Resource '" + path + "' contains inline GDScripts, will not load it.") return null - + + # Check all ext resources, and verify that all their paths start with res:// + # This is to prevent loading resources from outside the game directory. + # + # Format is: + # [ext_resource type="Script" path="res://safe_resource_loader_example/saved_game.gd" id="1_on72l"] + # there can be arbitrary whitespace between [] or the key/value pairs. the order of the key/value pairs is arbitrary. + # we want to match the path key, and then check that the value starts with res:// + # the type doesn't matter, as resources themselves could contain further resources, which in turn could contain + # scripts, so we flat-out refuse to load ANY resource that isn't in res:// + + var extResourceRegex:RegEx = RegEx.new() + extResourceRegex.compile("\\[\\s*ext_resource\\s*.*?path\\s*=\\s*\"([^\"]*)\".*?\\]") + var matches:Array = extResourceRegex.search_all(file_as_text) + for match in matches: + var resourcePath:String = match.get_string(1) + if not resourcePath.begins_with("res://"): + push_warning("Resource '" + path + "' contains an ext_resource with a path\n outside 'res://' (path is: '" + resourcePath + "'), will not load it.") + return null + # otherwise use the normal resource loader to load it. return ResourceLoader.load(path, type_hint, cache_mode) diff --git a/safe_resource_loader_example/another_unsafe_resource.tres b/safe_resource_loader_example/another_unsafe_resource.tres new file mode 100644 index 0000000..0a16358 --- /dev/null +++ b/safe_resource_loader_example/another_unsafe_resource.tres @@ -0,0 +1,13 @@ +[gd_resource type="Resource" script_class="SRLSavedGame" load_steps=4 format=3 uid="uid://cx4rf0ybe3u7o"] + +[ext_resource type="Script" path="res://safe_resource_loader_example/saved_game.gd" id="1_on72l"] + +[ ext_resource path="my_script.gd" type="Script" id="4711"] + +[sub_resource type="Resource" id="Resource_a4lfc"] +script = ExtResource("4711") + +[resource] +script = ExtResource("1_on72l") +health = 200 +metadata/hack = SubResource("Resource_a4lfc") diff --git a/safe_resource_loader_example/my_script.gd b/safe_resource_loader_example/my_script.gd new file mode 100644 index 0000000..b8996f2 --- /dev/null +++ b/safe_resource_loader_example/my_script.gd @@ -0,0 +1,2 @@ +func _init(): + OS.alert("Heya I just executed code via a side-loaded script!") \ No newline at end of file