Security, Arbitrary Code, and Expansion Packs

I’ve seen it pointed out a few times that having one’s program run arbitrary, externally-provided code is dangerous.

For example, having a localisation system that stores its text as string-variables in Python-files provides a potential attack-vector. (Even if the strings are never “exec”/“eval”-ed, the file would presumably be imported.)

However, eschewing arbitrary code seems to me to produce an impediment to expansion packs:

If the pack is more than a content update, if it introduces new logic–be it puzzle-mechanics, enemy behaviours, weapon types, or whatever–it would seem to call for the introduction of code that was not present in the base game.

(I suppose that one could require that all expansion logic be built using only core-game classes, but that seems likely to either be rather restrictive, or call for an impressive amount of foresight.)

So, how does one mesh these two concerns?

(It comes to mind now because I’m contemplating the implementation of certain features in my current project. Having them draw from Python-files would be convenient and enable expansions; having them draw from data-files would be safe.)

Lua does this really well (and is widely done), and is easily embedded and bridged:

Python… looks like there was a stab at it in the 2.x timeframe, but looks to be dead on the vine:

What would be the advantage of using Lua? I’m afraid that I’m not familiar with it.

In fact, this problem is far-fetched.

File: en.loc

[text]
0 =
1 = Not filled!
2 = Space!
3 = Invalid characters!
4 = Connect
5 = Disconnect
6 = You are connected
7 = You are disabled
8 = Connecting ...
9 = The server is not responding
10 = Exit
11 = Login:
12 = Password:
13 = Credits
14 = Video
15 = Audio
16 = Keyboard
17 = Mouse
18 = Game
19 = Language

Code:

from direct.showbase.ShowBase import ShowBase
from panda3d.core import TextNode, NodePath
import configparser

class Game(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        config = configparser.RawConfigParser()
        config.read('en.loc')

        label = TextNode('label')
        label.set_text(config.get('text', '18'))

        label_node = NodePath(label.generate())
        label_node.set_scale(0.1)
        label_node.reparent_to(aspect2d)

game = Game()
game.run()

You just need to use a different code design.

That… Doesn’t really address the problem that I described.

The matter of string-files was just an example; the central issue lies with game-logic. For that, text-files are not a solution, I feel.

Your question seems to be along the lines of:
“How could I allow other content-creators to write Python code which is added as an expansion pack to my core game in a safe way?”

I don’t see an easy way to achieve this, short of reviewing the submitted code yourself for each submitted expansion pack. Automated systems miss stuff all the time.

I’m not really worried about other content-creators; I’m thinking more of official expansions made by myself.

However, much of this concern of mine was prompted, as I recall, by reactions to an earlier version of my save-game system, which made use of “exec” and/or “eval”. And while save-swapping is a potential vector there, otherwise I would tend to expect saves to remain within a single system, or at least account.

But you do somewhat confirm my thought that perhaps it’s not worth looking too far for a way to do this, and to fall back on using Python for game-logic and the like…

Depends what risk-tolerance you have for your users.
Tldr: using lua is significantly easier than writing and hardening your own extensions runtime.

google: “safe python sandbox” → (approximately) can’t be done

Do your own due-dilligence (disclaimer, disclaimer, disclaimer…) but safe embedding is the primary use-case for Lua. How can I create a secure Lua sandbox? - Stack Overflow.

I see… Thank you for that.

I do wonder whether it’s worth it for the sake of creating expansions to a game.

Maybe it would be otherwise if I were planning on support for user-build mods, however.

Oops. I missed your “in-house expansions only” response. In that case…

<handwave standard-disclaimer=true>

  • in dev-mode, use exec to prepare ‘extension-code’
  • when done developing, securely sign your expansion pack
  • in production mode, only exec code from an expansion pack with a valid signature
  • since you’re now only exec-ing code you’re certain is yours, it should be pretty safe

</handwave>

Hmm, I see. Thank you for the explanation!

I have little familiarity with such secure signing, so I’m tempted to then drop the idea of separate expansions. But we’ll see, and I’ll likely give it some thought!

Cryptography signing your modules and verifying the signature before loading them is actually a very achievable feat.
The TLDR of it is you have a private key on your computer that you use to sign the extension module and a public key is included in your game and it checks the incoming module against the key before loading.
Here’s a quick stackoverflow.com post on the process of signing and verifying a file: ssl - How to verify a signed file in python - Stack Overflow

If you are thinking about yourself, then you probably don’t need to do anything. We need a simple installer that will update the game files.

I still don’t understand how this problem is related.

Thank you for that; I’ll likely take a proper look at the process given there later! :slight_smile:

But if the game will run just any expansion pack that’s given to it, then there’s a potential attack vector in someone either creating a new expansion pack or modifying an existing one and including malicious code in it. The game would then happily run that malicious code, potentially bringing harm to the user.

Related to what?

If you mean the text-string example, it’s simply that a Python-based approach to that (which is convenient, and thus tempting) introduces an attack vector, much as running an arbitrary expansion pack does.

If you look at it differently. Now add-ons for games are distributed in the form of patches. Which you need to download from an official source, which guarantees security. However, if this is done from another place, then no matter what programming language the game was written in, it is dangerous.

Hmm… That’s a fair point.

(Well, the sandboxing described above might at least mitigate some of the danger that you mention, in all fairness.)

That said, a malicious third-party program that inserted code into unprotected Python files could do some damage that way. But perhaps that’s an unlikely scenario.

Even if you’re cryptographically verifying an add-on with a private/public key, it would seem necessary to have a set of eyes review the initial submitted add-on code, whatever language it might be in. And even in that scenario you cannot entirely eliminate some attack vectors.

In the case of allowing arbitrary, unreviewed, code execution – this is not secure in principle, I think. An example of this might be downloading a random Python-based save game file from a random server on the net, and loading that up in a game.

Of course, no system is entirely secure. But an approach that uses “live” code seems likely to have more attack vectors than one that doesn’t.

Oh, of course! But I’m not thinking of that part of the process, really. I’m thinking more at the point of running the code on the user’s machine.

So let’s say, for argument’s sake, that all expansion packs are reviewed and found to be fine. If the core game runs them by virtue of “exec’ing” / “eval’ing”, or even just importing and running, then there would seem to be a potential security issue.

(I’m to some degree thinking back to an objection that I had to my save-game module some time ago, in which it was indicated that having such a module use “exec” and/or “eval” (I forget which or whether both) was, for the one objecting, uncomfortably insecure.)

Indeed, which is why I reworked my save-game module to not use “exec” and “eval”, as I recall. However, such a feat seems more difficult for something like an expansion-pack…

The only correct solution is to abandon the execution of the code on the python side. As a workaround, you need to develop your own scripting language for your application.

As an example, there is the concept of opcodes.

That would be quite an additional feature-set to take on I fear, however!

But, your earlier argument regarding official expansions does hold, I think; while something more sandboxed or, indeed as you suggest, internal and limited, might be wise for a user-available modding system, I’m beginning to feel that sticking with Python code might be okay for official expansions.