WARNING
This is a draft
Writing good code is hard. Writing secure code is more than hard. So currently, I’m encountering an issue on one of my project. My first (and last) article on medium was on Erlang External Term Format, a binary format used by Erlang to serialize its own data-structure and share them easily around the world.
In this article, I’ve made a simple snippet to retrieve PID information from Linux kernel based on sys-filesystem. When getting information from PID, you need to be root, or, using a privileged user who has the right to read everywhere. In normal time, this is not really a problem. Some static files need to be read by an isolated process… It works pretty well. In our case, PID files and content could contain not safe data.
Furthermore, if we run an Erlang node connected to another node with privileged users, we can execute commands in privileged user via RPC. This is a nightmare and no one want that. I’ve ask this question on stack-overflow (opens new window) and currently, no answers. So, how to make thing great again? I will try to answers it myself! 😉
# One Erlang node with privileged user
This is probably the worst method. You don’t really care about security, you’re probably running this node somewhere without internet and doing nothing import… I will not develop this method here.
# Two Erlang nodes, one privileged, one restricted
We can use this method easily. Erlang can be connected to multiple node natively but, there is a really big problem. When multiple nodes are interconnected with Erlang default protocol, every node can call RPC to any connected nodes. So, an unprivileged user can play with privileged nodes.
This problem could be solved by creating something like a firewall between each node and allowing only certain pattern to be passed… This is not a panacea because Erlang node can also share anonymous function and a firewall or any protection could be easily bypassed.
We can also define a more restricted and more secure protocol between those nodes, allowing only what we want, like a standard API.
In all case, this method will just make thing more difficult to maintain, because we need to managed two nodes on each servers.
# One Erlang node with sudo/doas command
sudo or doas (on OpenBSD) were made to give some privilege to some users. These privileges are most of the time the right to execute particular commands in privilege users.
Erlang give us the possibility to execute command in multiple ways…
# One Erlang node with privileged ports
An Erlang ports is a program connected to the BEAM.
# One Erlang node with privileged NIF
A NIF is a piece of code running directly on the BEAM and can alter its behavior and its stability.
# One Erlang node with privileged daemon
We can communicate through sockets or pipes, but we need to define a protocol between them.
# One custom Erlang node with security enforcement
Many Operating Systems offers now lot of method to mitigate attack. Capsicum is used on Linux/FreeBSD, pledge is used on OpenBSD, SELinux comes with a lot of Linux distribution… Lot of features, sometimes easy to implement, sometime a little hell to understand and use.
An other method commonly used now to make a program with privileged rights, is to use Privilege Separation method. When your program runs as root, this one fork itself with a less privileged user. These two processes share a pair of pipes to communicate. When less privileged process need something, a request to privileged process is made, and can be authorized or denied.
Erlang/OTP is running in VM, I don’t think its even possible to enforce security directly in this virtual machine without altering its behavior.
# What’s your secure project?
My project is a bit like osquery. I want to extract and collect kernel internal state easily. To many software need to run with high privilege without strong isolation between parts. Sometimes, running in root is the quick and dirty way to make things done.