godotengine / godot-proposals

Godot Improvement Proposals (GIPs)
MIT License
1.12k stars 69 forks source link

Provide a way to load resources without running scripts #4925

Open dploeger opened 2 years ago

dploeger commented 2 years ago

Describe the project you are working on

I'm the core developer of EgoVenture, a first person adventure game framework and a contributor to Escoria, a third person adventure game framework. Both projects are using Resources to provide a savegame functionality.

Describe the problem or limitation you are having in your project

As recommended in the official Godot docs, Resources should be used to store and retrieved structured data.

This can be used e.g. to persist the current game's state (aka savegame).

As many godot developers (GDquest, among others) has pointed out: Using resources to store data can be dangerous as resources can also hold scripts which are executed when loading the resource later.

That way, malicious people can send seemingly valid savegames and embed malicious code.

Describe the feature / enhancement and how it helps to overcome the problem or limitation

As it can be required to load scripts from Resources, Godot should feature a SecureResourceLoader, which specifically skips loading/executing scripts from resources so developers can still rely on resources as the most optimised method for storing structured data.

Describe how your proposal will work, with code, pseudo-code, mock-ups, and/or diagrams

Very simply put, SecureResourceLoader should extend ResourceLoader, override the load/load_interactive methods and not load and execute any scripts in the given Resource.

(this might sound way easier than it is to actually embed it, I still have to dig through the code)

If this enhancement will not be used often, can it be worked around with a few lines of script?

No, because loading resources is a core functionality.

Is there a reason why this should be core and not an add-on in the asset library?

Loading resources is a core functionality and security should be a core responsibility as well.

dploeger commented 2 years ago

Hm, so I guess the pragmatic way to implement it would be by adding a load_secure method to ResourceFormatLoader and e.g. clean the scripts from resource definitions when loading them e.g. in ResourceFormatText. The SecureResourceLoader would call load_secure instead of load.

Alternatively, we could implement this into the load function of ResourceLoader by adding a boolean secure parameter which defaults to false (to be backwards compatible)...

The not so pragmatic way would be to implement secure casting to resource for every object that could be loaded. But as this is only about .tres resources (if I got everything right), we maybe better implement it on a ResourceFormatLoader level.

YuriSizov commented 2 years ago

We've been discussing it a bit, and it should load scripts, because otherwise you lose the benefits of scripted resources. It should ignore bundled scripts and external scripts loading from the outside of res://.

Another option would be to validate the loading resource against some sort of schema. I can imagine you can refer to your internal type as a schema and the engine would strictly validate serialized members against it. This way you can avoid extra fields being added and extra types being used, preventing the possibility of a scriptless attack.

dploeger commented 2 years ago

The schema would not work that great in my context because it's variable for each game designed with the framework so adding a schema would put that burden on every developer using the framework.

Maybe instead something like an allow_scripts field in the Resource class which could be set to false or something?

YuriSizov commented 2 years ago

The schema would not work that great in my context because it's variable for each game designed with the framework so adding a schema would put that burden on every developer using the framework.

A schema would be resource based. So ideally it would require zero work from the developer beyond designing their resources.

Maybe instead something like an allow_scripts field in the Resource class which could be set to false or something?

Scripts are not the only attack vector here. And it shouldn't be a resource decision, it should be a loader decision. Because otherwise the bad actor can still replace the script in your resource that allows it with their script. Which is why it should filter out scripts based on their quality while loading: if they are bundled or if they are external and not from res://.

dploeger commented 2 years ago

@YuriSizov good call

NathanLovato commented 2 years ago

I'm all for it, and actually, if this proposal gets accepted and an implementation idea vetted, I'd like to offer to sponsor someone to implement this new feature.

TheDuriel commented 2 years ago

Just to bring this over from twitter:

It is my opinion that the correct way to solve this, would be to completely disallow embedding script within any kind of godot resource format. Be that resource, scene, or others. Yes, this would include removing the entire embedded script functionality. (Which already lost most of its usefulness due to lack of feature support for things like class names.)

(I been theorizing a way to inject code through the icon.png and default audio bus files present in all projects. Completely outside of the control of the developer.)

mieldepoche commented 2 years ago

this would include removing the entire embedded script functionality

I personnally use them sometimes, at least when prototyping.

I been theorizing a way to inject code through the icon.png and default audio bus files present in all projects. Completely outside of the control of the developer.

Do you mean, corrupting a pck file? You can litterally inject anything you want inside it really easily.

TheDuriel commented 2 years ago

No, this wouldn't touch the PCK at all. But it's irrelevant in any case.

NathanLovato commented 2 years ago

I'd also lean towards removing embedded scripts, which could be part of solving this, for all their limitations. The way they come to bite you when your project grows and you get errors in embedded scripts, for instance, you quickly lose the little time they saved you initially.

But that likely depends on finding some consensus, I remember quite a few people liked them.

Sslaxx commented 2 years ago

Not sure how relevant this might be, but https://twitter.com/reduzio/status/1549655657183416320 - @reduz here talks about a "JSON-like" format?

Calinou commented 2 years ago

I'd also lean towards removing embedded scripts, which could be part of solving this, for all their limitations. The way they come to bite you when your project grows and you get errors in embedded scripts, for instance, you quickly lose the little time they saved you initially.

This was proposed in the past and rejected: https://github.com/godotengine/godot/pull/54553

Since then, built-in scripts have been improved significantly, so I wouldn't remove them (even though I don't use them myself).

Not sure how relevant this might be, but twitter.com/reduzio/status/1549655657183416320 - @reduz here talks about a "JSON-like" format?

I think reduz is talking about ConfigFile here (whose syntax is closer to TOML than JSON).

Sslaxx commented 2 years ago

Is this also potentially an "abuse" of Resource and its ilk anyway, i.e. it was never intended to be used in this fashion and so any such use will be unsupported? If so, might be worth rewording the documentation to discourage this practice and/or preventing them from being saved to/loaded from outside of res://.

The use of "events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":16777232,"physical_scancode":0,"unicode":0,"echo":false,"script":null) and similar in files should also be forbidden outside of res://.

NathanLovato commented 2 years ago

I think it doesn't matter that it wasn't initially meant to be used that way if it's possible to support the use case. Resources are a great tool for modding, bringing in user-created data into Godot in general, they have the potential to save you a lot of code and coupling (because of how there's this built-in load() system that'll load resources only once), they can work as text in debug builds for quick bug fixing, and binary in release builds for smaller file sizes and faster loading...

They have pros, they're just lacking several small improvements, in this case for user safety.

NathanLovato commented 2 years ago

This was proposed in the past and rejected: https://github.com/godotengine/godot/pull/54553

Since then, built-in scripts have been improved significantly, so I wouldn't remove them (even though I don't use them myself).

Makes total sense, yeah, if the community uses them then built-in scripts are here to stay.

SilencedPerson commented 2 years ago

I'd also lean towards removing embedded scripts, which could be part of solving this, for all their limitations. The way they come to bite you when your project grows and you get errors in embedded scripts, for instance, you quickly lose the little time they saved you initially.

But that likely depends on finding some consensus, I remember quite a few people liked them.

Maybe it could be useful to not allow embedded scripts on load?

NathanLovato commented 2 years ago

Yes that's already an idea in this conversation, see these two comments by @YuriSizov:

dalexeev commented 2 years ago

Personally, I don't see a problem with the .tres, .res, etc. formats supports script embedding. The problem only arises when this format is used for purposes for which it was not intended.

In my opinion, .tres is not intended for save files and similar external files, the purpose of which is to store data, and not extend the functionality of the game. And it's not just security, but other aspects that I can talk about, but they are beyond the scope of this discussion.

And as mentioned above, you can safely use Resource for external files, but you need to use ResourceFormatLoader for that, not .tres/.res.

At the same time, I am completely in favor of having an analog of the allow_objects parameter in str2var, ConfigFile, etc.

Sslaxx commented 2 years ago

Custom resources (in 3.x) require C++ modules, which may be beyond the scope of some people's intended usage, or skills.

YuriSizov commented 2 years ago

In my opinion, .tres is not intended for save files and similar external files, the purpose of which is to store data, and not extend the functionality of the game.

That's pretty arbitrary. Resources are just scriptable objects with built-in serialization capacity. In C# land you can serialize any class with XML in the same way, and it's used in games all the time. I think, that's the go-to way in Unity, at least from my experience with digging into game saves of games made with it.

So from the ease of use perspective and the level of integration into the engine, custom resources are perfect for storing user data.

dploeger commented 2 years ago

That's also exactly what the docs say. 😉

dploeger commented 2 years ago

Custom resources (in 3.x) require C++ modules, which may be beyond the scope of some people's intended usage, or skills.

No, you can just extend the Resource class and you get a custom resource. I heavily make use of that in EgoVenture.

Sslaxx commented 2 years ago

This may be somewhat addressed by https://github.com/godotengine/godot-proposals/issues/5010

Proggle commented 2 years ago

One of Godot's big strengths is streamlining common gamedev tasks, so you don't have to spend time reinventing the wheel. Saving and loading the game is one of the most common functions that devs need to implement (up there with having collision detection), but currently it isn't as straightforward as it could be.

And of course, from the game developer perspective, as far as the player's experience goes, the only real question that needs to be answered is "What data goes into the savefile?". Whatever method the developer uses to cram the data into a save file, and then pull it back out unchanged, will function equally well.

So, since this is a common feature where the exact algorithm doesn't matter to the end product, the developer should be able to just trust a built-in Godot feature to do it properly, and not worry about what's going on under the hood.

As is, resources are almost the perfect way for Godot to handle saving and restoring states. It uses Godot's unique strengths to handle savegames easily, and doesn't require futzing around with special cases to handle unsupported datatypes (like Vector2s in json).

It's unfortunate that this method is currently unsafe, but if inputs can be sanitized, it's a perfect way to never need to think about the underlying savegame/loadgame algorithms ever again.

TheDuriel commented 2 years ago

Or just, save the resource using File, not the ResourceSaver, and encrypt it.

Or, since your savegame boils down to just being a dictionary anyways. (And lets be honest, custom Resources are just fancy typed dictionaries.) Just use File.store_var()

Resources as savegames only blipped up recently as a consideration, since I think people have finally caught on that json text files are a poor idea. But the next step is raw binary data dumps. Saving and loading a resource is actually more (or at least identical) work than making a dictionary and saving/loading that.

But that isn't even the topic of this issue. The topic is the glaring security problem caused by Godot automatically loading resources that could contain malicious code. Which is only a problem in two cases: A developer using resources where normal files would be sufficient. Or a malicious agent placing resources where it will override the pck contents to inject code. (Which requires the developer to have omitted the res.// in the file path.)

dalexeev commented 2 years ago

I agree that File.store_var/get_var in combination with a dictionary is already sufficient for a convenient implementation of save files. Although the analog of allow_objects should be added to the VariantParser, so that text serialization (for example, ConfigFile) also becomes safe.

tscn/tres are not suitable for save files, not only because of security, but also because of compatibility. This format is simply not intended for such purposes, as it contains references to external resources and contains nested resources, that is, it makes the save file dependent on their implementation. Attempting to load a save file based on resources from an old incompatible version of the game will result in an error loading the resource, while json, ConfigFile or a serialized dictionary can be loaded without error and then converted to the new version.

alekh-ai commented 1 year ago

Is there any way to use unique tokens for custom resources. That way it will be safer to use.

Calinou commented 1 year ago

Is there any way to use unique tokens for custom resources. That way it will be safer to use.

Are you referring to encryption or signing of custom resources? That's a topic for a different proposal. Godot already offers way to save/load encrypted files, but I doubt there's a built-in signing mechanism you could make use of.

lyuma commented 1 year ago

Hi, sorry I never replied with the implementation we use in V-Sekai since last year. @fire pinged me to reply to this issue. If you're interested you can find the solution we use here: https://github.com/V-Sekai/godot/tree/resource_loader_whitelist

This feature was rejected from core due to security considerations, but I think another discussion is needed. I'll explain.

I suspect this patch is sufficient for the types of resources that a game, for example, might use to store a save game or a level, usually a custom extends Resource class being written to disk.

While we (the V-Sekai team) suspect the above patch solves the pertinent problem to resources in particular, it does not make all types of resources entirely secure. It is critical that scenes in particular be further validated in terms of node paths or animations, and possibly other cases.

Also, once the resource is loaded, most of the other security risks can be handled in script through a combination of recursion and property validation. [Here is an example of one such possible implementation.])https://github.com/V-Sekai/v-sekai-game/blob/main/addons/vsk_importer_exporter/vsk_map_validator.gd) Note that this type of validation logic is not secure in general, and must be adapted to your game.

That said, because of its applicability to extends Resource type resources commonly used for saved games and other user-sharable files, I do think the mitigation in our implementation would go a long way and I'd love to see it in core, even if imperfect. (As a user, I will lament the loss of widespread ability to mod Godot games through save game editing, but it's for the best.)

reptofrog commented 1 year ago

It would've been great to have an ability to granularly restrict what loaded scripts can and what they can't do, something that was proposed in here: #4642 in conjunction with #5010

This would enable the creation of games that can be modded safely without resorting to any extensions or custom solutions.

derkork commented 11 months ago

I have created a small library that should solve this problem for saved games. It basically works similar to the whitelist @lyuma posted, but it will flat-out refuse to load any resource file that contains resources of type GDScript. This will probably not be good enough for user generated content where you may want to allow some sandboxed scripting, but it should work decently for saved games which usually will not contain any inline scripts, so it's a nice 80/20 solution I think.

TheDuriel commented 11 months ago

A resource is entirely capable of pointing towards a script definition outside of itself, or even outside of the project.

derkork commented 11 months ago

Yes it is. But for this to work, the player would have needed to download and place this script in addition to the save game file on their computer, so I wouldn't think this is a practical attack. That being said, if you have comments regarding the library it may be better to open up an issue there instead of using this discussion thread.

lyuma commented 11 months ago

I'm sorry I forgot to link this here. Last month I wrote a GDScript resource parser which can be used for validation before loading the resource, without any need for engine support: https://gist.github.com/lyuma/8de4620a402d565b86e1287150c8fb31

It's a bit scary in that it's security critical code, but on the plus side, in this form it would be fairly easy to audit and ensure it functions identically to the resource loader.

The risk of this approach is if Godot's resource parser ever diverges from this code (one if statement is different, for example), then that difference in code path could be used to create an exploit. My expectation is Godot would increase FORMAT_VERSION if it changes the format, but if its parsing code is not careful and they add the new features to the old format, it would create an exploit.

I think this approach would be safe if it explicitly checks for a specific Godot major and minor version (e.g. 4.2) and human review is done on each version to ensure it continues to match parsing logic. Personally, I'm willing to take this approach because I think it is less error prone than the failure case of accidentally using an unpatched engine, or having validation built into Godot and having security critical code decay by mistake during an unrelated change.

Let me know what you think about this design.

theraot commented 10 months ago

Since nobody seems to have mentioned it, I'll point out that if loading resources can skip scripts it would also address https://github.com/godotengine/godot/issues/65393

Mohamed-Kr commented 1 month ago

I don't have a very deep understanding of what can be done or not, but maybe we can change the Resource class, we could give it functions like filter_scripts_on_load()->bool and smth like allowed_scripts_on_load()->Array, that would give information on what can be loaded or not and is called only if the former function returns true

And we could have ResourceLoader.safe_load that would take something like a class as an argument and load only if what is loaded is of that class, and then follow the behaviour for the loading of the scripts provided by the loaded Resource

addmix commented 1 month ago

PackedScenes separate loading from instantiation, and I believe a similar approach should be possible with Resources as a whole. I suggest that the ResourceLoader can have an optional parameter to load resources (and by extension, PackedScenes), but while prohibiting/delaying script initialization/execution.

It's not quite the same, but I think PackedScene's GenEditState design concept could be used for this purpose. If you can load/instantiate items, and modify them before any script is allowed to run, you could filter out any scripts or unwanted content, before any execution has occurred.

The usage would be like this:

ResourceLoader.load("user://path/to/UGC.res", "", CacheMode.CACHE_MODE_REUSE, LoadingMode.LOAD_MODE_EDIT)

the LoadingMode.LOAD_MODE_EDIT would cause scripts to be prevented from all forms of execution, until the resource is allowed to continue script execution.

This also naturally opens the door for scripting to filter/process resources and scenes before they can effect anything within the system.

gabrielesilinic commented 2 weeks ago

I'd like to pop in for a minute to give my suggestion.

probably stripping down scripts could be kind of a bad idea for some use cases.

The optimal way would have been having gdscript work more like javascript and having a sandboxed language for the most part. unfortunately this seems to be impossible. So the laziest but most effective solution would be to sign every single script that is part of the project (or have scripts be a resource that does not get directly packed into the save but just linked).

this can be as simple as a list of hashes separated by the script (unique) name on even on a text file possibly ordered just for the sake of binary search.

This way you would not lose any benefit coming from resources but you would prevent arbitrary code execution (since only a set amount of very specific scripts can be ran).

also by having the thing be a list any mod manager script could add their own scripts to such list. obviously this relies on the user's intelligence that he won't run any malicious script which would add itself to such signature registry. but at this point if signing is compromised in such a way it becomes the user's problem, because he ran something untrusted where the attack vector could have been a thousand other things.

though if we know the user is extra dumb we may want to have a few options where to put maybe an encryption key (like the registry in windows) just so everything breaks whenever a user arbitrarily replaces the signature registry file beside the game without putting much thought into the process, it's fine if a willing user or script willingly ran by the user can retrieve the key from the registry or whatever other place itself.

This is not 100% foolproof but should prevent the least determined fools from getting in trouble.

Pidermonkey24 commented 1 week ago

I'm not entirely sure if this is even remotely possible but would it be possible to have a parameter that would not allow loaded resources to use certain libraries like OS, HTTPS, or Filesystem? This definitely wouldn't be fool hardy in the slightest and I'm unsure if it's even possible in 4.x but it would likely deter a non zero amount of bad actors.

Calinou commented 1 week ago

I'm not entirely sure if this is even remotely possible but would it be possible to have a parameter that would not allow loaded resources to use certain libraries like OS, HTTPS, or Filesystem?

Yes, but this is not the topic of this proposal (which is only about not running scripts at all). See https://github.com/godotengine/godot-proposals/issues/5010 instead.