-
Pl
chevron_right
Philip Withnall: Parental controls web filtering backend
news.movim.eu / PlanetGnome • 2 days ago - 13:25 • 5 minutes
In my previous post I gave an overview of the backend for the screen time limits feature of parental controls in GNOME. In this post, I’ll try and do the same for the web filtering feature.
We haven’t said much about web filtering so far, because the user interface for it isn’t finished yet. The backend is, though, and it will get plumbed up eventually. Currently we don’t have a GNOME release targeted for it yet.
When is web filterings? What is web filtering?
(Apologies to Radio 4 Friday Night Comedy .)
Firstly, what is the aim of web filtering? As with screen time limits, we’ve written a design document which (hopefully) covers everything. But the summary is that it should allow parents to filter out age-inappropriate content on the web when it’s accessed by child accounts, while not breaking the web (for example, by breaking TLS for websites) and not requiring us (as a project) to become curators of filter lists. It needs to work for all apps on the system (lots of apps other than web browsers can show web content), and needs to be able to filter things differently for different users (two different children of different ages might use the same computer, as well as the parents themselves).
After looking at various different possible ways of implementing it, the best solution seemed to be to write an NSS module to respond to name resolution (i.e. DNS) requests and potentially block them according to a per-user filter list.
A brief introduction to NSS
NSS (Name Service Switch) is a standardised name lookup API in libc. It’s used for hostname resolution, but also for user accounts and various other things. Names are resolved by various modules which are
dlopen()
ed into your process by libc and queried in the order given in
/etc/nsswitch.conf
. So for hostname resolution, a typical configuration in
nsswitch.conf
would cause libc to query the module which looks at
/etc/hosts
first, then the module which checks your machine’s hostname, then the mDNS module, then systemd-resolved.
So, we can insert our NSS module into
/etc/nsswitch.conf
, have it run somewhere before systemd-resolved (which in this example does the
actual
DNS resolution), and have it return a
sinkhole address
for blocked domains. Because
/etc/nsswitch.conf
is read by libc within your process, this means that the configuration needs to be modified for containers (flatpak) as well as on the host system.
Because the filter module is loaded into the name lookup layer, this means that content filtering (as opposed to domain name filtering) is not possible with this approach. That’s fine — content filtering is hard, I’m not sure it gives better results overall than domain name filtering, and means we can’t rely on existing domain name filter lists which are well maintained and regularly updated. We’re not planning on adding content filtering.
It also means that DNS-over-HTTPS/-TLS can be supported, as long as the app doesn’t implement it natively (i.e. by talking HTTPS over a socket itself). Some browsers do that, so the module needs to set a canary to tell them to disable it. DNS-over-HTTPS/-TLS can still be used if it’s implemented by one of the NSS modules, like systemd-resolved.
Nothing here stops apps from deliberately bypassing the filtering if they want, perhaps by talking DNS over UDP directly, or by
calling secret internal glibc functions to override
nsswitch.conf
. In the future, we’d have to implement per-app network sandboxing to prevent bypasses. But for the moment, trusting the apps to cooperate with parental controls is fine.
Filter update daemon
So we have a way of blocking things; but how does it know what to block? There are a lot of filter lists out there on the internet, targeted at existing web filtering software. Basically, a filter list is a list of domain names to block. Some filter lists allow wildcards and regexps, others just allow plain strings. For simplicity, we’ve gone with plain strings.
We allow the parent to choose zero or more filter lists to build a web filtering policy for a child. Typically, these filter lists will correspond to categories of content, so the parent could choose a filter list for advertising, and another for violent content, for example. The web filtering policy is basically the set of these filter lists, plus some options like “do you want to enforce safe search”. This policy is, like all other parental controls policies, stored against the child user in accounts-service .
Combine these filter lists, and you have the filter list to give to NSS in the child’s session, right? Not quite — because the internet unfortunately keeps changing, filter lists need to be updated regularly. So actually what we need is a system daemon which can regularly check the filter lists for updates, combine them, and make them available as a compiled file to the child’s NSS module — for each user on the system.
This daemon is
malcontent-webd
. It has a D-Bus interface to allow the parent to trigger compiling the filter for a child when changing the parental controls policy for that child in the UI, and to get detailed feedback on any errors. Since the filter lists come from third parties on the internet, there are various ways they could have an error.
It also has a timer unit trigger,
malcontent-webd-update
, which is what triggers it to periodically check the filter lists for all users for updates.
And that’s it! Hopefully it’ll be available in a GNOME release once we’ve implemented the user interface for it and done some more end-to-end testing, but the screen time limits work is taking priority over it.