Jeff Johnson (My apps, PayPal.Me, Mastodon)

Little Snitch "denied" connections leak your IP address: Developer response

June 12 2023

About a month ago I wrote a blog post and a follow-up about how Little Snitch—and all network content filter extensions, as it turns out—leak your IP Address. Last week the developer of Little Snitch responded to my blog posts, though they didn't directly mention me:

There has been some discussion recently about the bypassing of Little Snitch by the first datagram of a three-way TCP handshake.

I guess it literally goes without saying that this discussion was spurred by my blog posts. Another strange thing about their response was the timing, which was the morning after the WWDC keynote, so it mostly got lost in all of the other big news. I didn't have much time to look at it last week, so I'm coming back to it now.

In my follow-up blog post, I mentioned that I filed a bug report with Apple (FB12088655 "Privacy: Network filter extension TCP connection and IP address leak"). I've received no response from Apple to my bug report. However, the developer of Little Snitch (Objective Development) claims that this behavior is by design, not a bug, and indeed defends the design.

Before I address the defense, I want to emphasize that IP address leaks are a serious privacy problem. It's one of the reasons that VPNs exist. Apple's own iCloud Private Relay, which is similar to a VPN in some ways, is also designed to hide your IP address. Thus, I think we need to start with the assumption that network filter extensions ought to avoid leaking your IP address, if possible.

Objective Development's blog post talks a lot about latency:

When a connection should be established, the data must be passed from the kernel to an Apple user space process and from this user space process to the respective Network Extension. The Network Extension makes a decision based on rules, sends back the result via to the original user space process which in turn sends it down to the kernel. A long path, isn‘t it?

When you open a web page in a browser, it‘s quite possible that 10 to 100 more connections are opened (to various trackers and ad servers, by the way) and delaying each connection only by a few milliseconds would degrade perceived performance. This is of course a red rag for Apple. They want to be the best and fastest and browser benchmarks are a common way to compare systems. That‘s the most relevant operation for most users, after all.

I feel that bringing up browser benchmarks is a red herring that doesn't help the argument at all. Every browser on the Mac is affected equally by a network extension like Little Snitch; it makes no difference whether you use Safari, Google Chrome, or Firefox. Perhaps Objective Development is referring to Mac vs. Windows comparisons, but that's still a red herring and makes little sense, because benchmarks would not typically be taken with a network filter extension installed. Little Snitch is not a built-in macOS component but rather an optional third-party app, used by only a small minority of Mac users. This small minority tends to care deeply about their privacy, which is why they spent the money to purchase Little Snitch.

I'm not persuaded that performance over privacy is a good tradeoff for network extension users. And we don't even get the choice. Apple is imposing its decision on everyone, with no options. And speaking of performance, do you know what else can degrade it? iCloud Private Relay! Apple essentially admits this while trying to dance around the fact:

Private Relay uses a single, secure connection to maintain privacy and performance. This design may impact how throughput is reflected in network speed tests that typically open several simultaneous connections to deliver the highest possible result. While some speed test measurements may appear lower when Private Relay is enabled, your actual browsing experience remains fast and private.

Additional latency may be the price of protecting your privacy, and that's a price I'm willing to pay. Moreover, network latency due to the distance between client and server, especially for intercontinental connections, may dwarf whatever local process latency exists between kernel and network extension. Although I do wonder whether Apple's design here is inherently flawed. Why is the kernel waiting on user space?

Compare network content filter extensions with Safari content blocking extensions. With the latter (my own StopTheFonts is an example), the content blocking extension declares a set of static blocking rules, and then Safari itself does all of the work of applying the rules, in Safari's own process space; the extension process doesn't even need to be running at the time. Whenever the user changes the content blocking rules in the app, the app simply tells Safari to reload the rules. Safari is never waiting on the content blocking extension to provide a verdict on individual URL loads.

It seems to me that Apple could do network content filter extensions the same way. Why couldn't Little Snitch provide its rules to the kernel in advance and let the kernel itself do all of the filtering, without having to switch contexts? Of course, when there's an "Ask for Connections" rule, the kernel would have to call out to user space, but in that case there's no reason for the kernel to optimize by starting the TCP 3-way handshake, because presenting a dialog to the user and waiting for a manual response already introduces a huge, potentially unbounded amount of latency. (For those not familiar with Little Snitch, it has 3 types of rule: "Allow Connections", "Deny Connections", and "Ask for Connections".) A design analogous to Safari content blocking extensions would seem to mitigate some of the latency that Objective Development mentioned, without sacrificing privacy. Even in those cases where deep packet inspection is required to associate the IP address with a URL host, that work could also be performed directly by the kernel instead of the user space process.

One of the questions I raised in my blog posts was not answered by Objective Development: why does Little Snitch leak your IP address on every TCP connection attempt, when LuLu and my own sample network filter extension do not? Describing the implementation of the earlier version 4 of Little Snitch, which was based on their own kernel extension, Objective Development explains why the TCP 3-way handshake was allowed in some limited circumstances:

This procedure (a lightweight version of Deep Packet Inspection) was only needed if there were multiple names known to resolve to the given IP address.

The necessity of deep packet inspection in some cases doesn't explain the current behavior of Little Snitch 5, which allows the 3-way handshake in all cases. And regardless, I would like the option, as a user, to enable or disable this behavior, so that I can control my own privacy level. I don't want Apple or Objective Development deciding that for me.

Jeff Johnson (My apps, PayPal.Me, Mastodon)