Memory quota tracking in Arti, for Onion Service DoS resistance

Last week we released Arti 1.3.0,
the latest version of our rewrite of Tor in Rust.
One new feature in this release is memory quota tracking.

Tracking and restricting memory for queued data

The memory quota tracking feature allows you to restrict the amount of memory used by an Arti process.
In particular, it allows you to limit the amount of memory
that other people can cause your Arti to use.

This is particularly important when Arti is being used
to provide an Onion Service (aka a Tor Hidden Service).
Running an Onion Service means letting users from all over the network
connect to your service
(depending, to an extent, on your configuration settings).
That means those users can cause your system to do work,
and, generally, to store data in transit to and from your Onion Service.
In 2014, Jansen et al discovered that this kind of data storage can
even be used to help deanonymise your service.

We have now implemented the recommended countermeasure:
Arti can track how much data is stored in its various queues.
When the configured limit is reached,
Arti starts shutting down connections, and discarding data,
until the queued data is below the limit.
We kill the connections with the oldest oustanding data.
This minimises the impact on unrelated, innocent, traffic.

We’ll also need this memory limit feature for Arti Relay,
which is currently being developed.

Configuration

In Arti, the memory quota tracker is controlled
by the [system.memory]
configuration subsection in arti.toml.
You can enable it by writing something like this:

[system]
memory.max = "1 GiB"

The feature is compiled in by default.
Setting the limit for the first time requires an Arti restart.
After that, adjusting (or removing) the limit can be done
at runtime.

There is also a memory.low_water setting:
When Arti needs to free memory because max is exceeded,
it keeps tearing down connections until the usage is below low_water.
This hysteresis helps stop the system oscillating.
The defaualt value of low_water is 75% of max.

(Note that unlike C Tor’s MaxMemInQueues setting,
the current default in Arti is not to enable a memory limit.
In Arti you must turn on the feature explicitly, by setting max.
We hope to get more experience of how it works for users in practice,
before we consider whether to enable a limit by default.)

Logging

After you’ve enabled memory quota tracking, you should see Arti print a log message like this:

2024-10-31T16:55:55Z  INFO tor_memquota::mtracker: memory quota tracking initialised max=1.00 GiB low_water=768 MiB

You can tell if memory reclaim has been triggered:

2024-10-31T17:22:19Z  INFO tor_memquota::mtracker::reclaim: memory tracking: 1.86 GiB > 1.00 GiB, reclamation started (target 768 MiB)
...
2024-10-31T17:22:20Z  INFO tor_memquota::mtracker::reclaim: memory tracking reclamation reached: 44.3 KiB (target 768 MiB): complete

Caution: very new code!

This is a very new feature.
There is a lot of complexity behind the scenes,
and by its nature it is difficult to do a full-scale integration test.
It is quite possible that there are bugs!
We’d like to hear your feedback, when you enable this feature.

You can report issues you discover
in our gitlab
(also available via an
anonymous ticket reporting system).
You can also contact us informally by email, or on irc:
we’re in #tor-dev on OFTC.

Thanks to our sponsors

Thanks to
Zcash Community Grants
for their funding,
which enabled the development of this feature,
and of course to our other sponsors
for funding the development of Arti.

Link to original source