Plugin architecture with Rust

I am working on a project that should be extensible with plugins. I am not fully sure what would be a good solution to write a plugin extensible project with Rust. I was wondering if some people have experience or ideas about this subject. I have never done something like this before, so any help is appreciated.

Some details:

  • The main program is an executable (Compiled from Rust code), and it exposes a pretty small set of API messages.
  • Users should be able to create plugins in any language they like (Not only Rust), so a plugin should be some executable file on the filesystem.
  • The plugins system should be cross platform. To be more specific, I want it to work on Linux, Android and Windows.
  • The communication between the plugin and the main program has to be secure. (Can not be read or intercepted).

In the first development iteration I wrote sockets interface. This approach is very generic, but the main problem I have with it is that it feels like a system with too many moving parts:

To add a new plugin, the user needs to register a new public key in the main program's configuration file, then add a private key for the plugin and put it somewhere safe. Finally the user runs the main program, and then runs the plugin. The plugin then connects to the program and authenticates itself using his private key. I I can't expect a non technical user to go through this process.

My current idea is as follows: The program will contain a configuration file with a list of paths to plugins:

  • Plugin1: /path/to/plugin1 + flags?
  • Plugin2: /path/to/plugin2 + flags?
  • ...

When run, the main program will first initialize itself, and then spawn the plugins as processes. To make the communication secure I thought about some ideas:

  • Socket + secret private key: The main program generates a private key and somehow passes it securely to the plugin. Then the plugin will connect using a socket to the main program. This method saves lots of configuration effort from the user, as described above. However, I couldn't find a cross platform way to pass the secret private key to the spawned plugin process. I checked some ideas like passing the secret information using command line arguments or environment variables but they seem to be insecure. I also checked the idea of using a temporary file to pass the information, but I am not sure how to use this method securely on all the mentioned platforms (Especially on windows).

  • Using stdin/stdout: The main program spawns the plugin as a new process, and uses stdin/stdout to communicate with the plugin. When the i/o closes, the plugin closes itself gracefully. I am pretty sure that the stdin/stdout idea works on linux and has reasonable Rust interface (Command in std::process - Rust), but I couldn't understand if this will work on Windows. I'm also not sure if this is expected to work on Android. One more thing I'm not sure about is whether this method is secure for communication.

I like the second idea (stdin/stdout) more at this point, but of course, I might be missing something.

Regarding serialization: I'm using Cap'n proto. This is what I used for the rest of the sockets code, and I thought it should probably work well with any other kind of communication.

If you have some experience with this kind of plugins architecture or have any ideas about what I wrote above I will be very happy to know about it!

1 Like

What's your threat model? Who's the attacker, and what sort of attack you're trying to protect against?

If you're protecting against attacks from other unprivileged users on the same multi-user system, then filesystem permissions protecting sockets, or stdio, should be safe (no encryption needed).

If you need anything stronger, like anti-cheating or DRM protecting from someone with root-level access, then encrypting the communication channel is useless, because the attacker can read your program's RAM and the data before encryption/after decryption.

1 Like

@kornel: Thanks for the repy.

I forgot to add this information!
The main program is used to manage financial assets of the user. If an unauthorized entity obtains access to the main program API, the user could lose money.

The protection is mainly against low privileged programs running on the same operating system, or other users using the same operating system. For example, it shouldn't be possible for unauthorized program to connect to the main program's socket and run commands. It also shouldn't be possible for another user on the system to run commands through the main program. I understand that there is no way for me to protect against the system's root, because he has full control over the system.

Could this be done in a cross platform way using Rust? My experience with sockets is that if I open one, anyone could connect to my socket. I planned to have the main program spawn a plugin subprocess and then hand over some secret to the plugin subprocess, so that it could authenticate itself when it connects to the main program socket. However, I couldn't find a cross platform way to pass this kind of secret to the spawned subprocess plugin, (maybe except for stdin/stdout ?)

Regarding permissions: I assume that this is a solution that will not work on Windows, right?

If the main program is spawning the subprocess, then the spawn can give the subprocess an anonymous pipe to use to communicate with the main process. There is no way to hijack it, since it doesn't have a name. In the olden days (circa 1996) I had that working on Unix and Windows.

2 Likes

Port — yes. Socket — only if they have access to that path on the disk.

Protecting from rogue programs on the same computer running with same user's privileges may be quite hard. Even without plugin systems, it's tough, since other programs may be able to read and control your program's UI.

Could this be done in a cross platform way using Rust?

Basic spawning of subprocess and communication via stdio is in the stdlib.

Beyond that, you'll most likely end up with separate solutions for Windows and non-Windows. Things like trusted keyboard input, isolated windows are platform specific.

Servo does a bit of sanboxing, and its portable, so some components may be useful to you:

1 Like