Possible lack of permission check

Hello,

I recently wrote a test application which I run on my personal iPhone. The two-thirds of the application are written in Rust and the other one-third is written in Swift with SwiftUI. When I run the core Rust application on my MacBook I get a permission prompt (expected) when the application requests a TCP socket to listen over the local network. When I run the exact same code on the iPhone no permission prompt is shown the application is just allowed UDP broadcast access over the local wifi and TCP listen over the local wifi without any "Local Network" permission. I feel like this should at least be behind a permission prompt like on macOS, am I right?

On the iOS side, working on local networks requires the Local Network privilege. For more background on that, see the Local Network Privacy FAQ.

macOS does not support the Local Network privilege, and neither does the iOS simulator. So I wouldn’t expect to see a local network prompt there. However, macOS does support the application firewall (System Preferences > Security & Privacy > Firewall), which may present authorisation prompts if your app attempts to listen for incoming connections.

Are you perhaps mixing these things up?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks for your reply. I think you mis-understood part of my problem. I of course do have Firewall enabled on macOS so that's why I get the local prompts (which I expect) but you didn't understand that I'm NOT using any simulator or Xcode preview I actually meant "running on my personal iPhone" I can even tell you that it is an iPhone 13 Pro running latest iOS 15.5 and the application Rust core component is built using target "aarch64-apple-ios" (aarch64 is the processor architecture for most Apple devices now). So I really meant that on my personal iPhone (and that is not a simulator, it's an actual physical device), my application is allowed to open TCP listen sockets and send UDP broadcast packets on the local wifi network WITHOUT any permission requirement (not even "Local Network")!

Is this more clear now?

my application is allowed to open TCP listen sockets and send UDP broadcast packets on the local wifi network WITHOUT any permission requirement (not even "Local Network")!

Opening TCP listening sockets is not an operation protected by LNP. See LNP FAQ-2.

Sending UDP broadcast packets should trigger LNP, and it certainly did the last time I tried it [1]. Are you sure those packets are going out on the ‘wire’?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Which, admittedly, was a while ago, because LNP was a really hot issue back in the iOS 14 timeframe but has quietened down a lot recently.

Opening TCP listening sockets is not an operation protected by LNP. See LNP FAQ-2.

Yeah I just read that on the FAQ you sent to me, which I find strange considering that listening for TCP on the local network technically accesses the local network.

Sending UDP broadcast packets should trigger LNP, and it certainly did the last time I tried it [1].

That part is surprising because according the FAQ you sent me I should be disallowed to do this completely because I didn't pay for the 100$ a year and UDP broadcast requires a special entitlement but in my case it requires nothing the only flags I've enabled in my Info.plist are:

  • "Supports Document Browser" -> Internet said this was required to support the Files app on iOS (linked with the second permission).
  • "Supports opening documents in place" -> Allows editing of all configuration files and lua scripts directly on the iPhone and allows viewing core application logs on the iPhone.
  • "Application supports iTunes file sharing" -> Allows my mac Finder to put lua files and other configuration files directly in the application documents directory.

Are you sure those packets are going out on the ‘wire’?

Well I'm sure these packets are sent otherwise how could I see the UDP packet from the client app running on my Mac?

Let me give you a part of the low-level network code so you can see by yourself (the actual code is in Rust but I tried to translate it to C):

pub const DEFAULT_PORT: u16 = 4026;

let packet = [/* some bytes here */];
let socket = UdpSocket::unbounded(Domain::IpV4).unwrap();
socket.set_broadcast(true).unwrap();
if let Err(e) = self.socket.send_to(&packet, (Ipv4Addr::BROADCAST, DEFAULT_PORT)) {
    eprintln!("Failed to send broadcast auto-discover packet: {}", e);
}

Here is my translation in C (using BSD names):

#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdio.h>

#define DEFAULT_PORT 4026
char packet[] = {0, 1, 3}; //This is just for example; the actual packet is more complicated.
int s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
int flag = 1;
setsockopt(s, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(int));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = (in_port_t)htons(DEFAULT_PORT);
addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
if (sendto(s, &packet, 3, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)
    fprintf(stderr, "Failed to send broadcast auto-discover packet");

Yeah I just read that on the FAQ you sent to me, which I find strange considering that listening for TCP on the local network technically accesses the local network.

The purpose of local network privacy is to preserve privacy. Specifically, the goal is to prevent apps from fingerprinting users by grovelling through their local network environment. It’s hard to fingerprint a user by opening a listening socket.

Well I'm sure these packets are sent otherwise how could I see the UDP packet from the client app running on my Mac?

Are you sure they’re going via the Wi-Fi interface? If you have the iOS device connected via USB, as is common when testing apps, these packets could be travelling via the virtual interface set up over the USB.

If you install your app using the Mac and then disconnect the USB, do you still see broadcast traffic on the Mac?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

If you install your app using the Mac and then disconnect the USB, do you still see broadcast traffic on the Mac?

The app was already installed from a previous experiment. I used the already installed app with the device completely disconnected from USB, kept only wifi and cellular network. The app is still visible by the mac tool although the connection is now a bit slower (the TCP part takes a bit longer to actually connect) but both the UDP and TCP parts are still active and I can still see the application running on the device popping up on the Mac client.

EDIT: I can add that the Mac client measured that the Rust component of the iOS application takes about 17 seconds to execute which is slightly faster than the Mac itself.

This is interesting. Some factoids:

  • Your Rust code is almost certainly using BSD Sockets.

  • Sending an UDP broadcast should trigger the local networking privacy alert, per LNP FAQ-2.

  • Sending an UDP broadcast should require the multicast entitlement, per LNP FAQ-3. For BSD Sockets clients this is only enforced in iOS 14.5 and later.

I tested this on iOS 15 (15.5 and 15.6 specifically). I do see the LNP alert, which is what I expect. However, if I agree to that alert, I see the broadcast go out on the ‘wire’ even though my app isn’t signed with the multicast entitlement.

I discussed this second point with the LNP team here at Apple and they agree that this seems like a bug. I’ve filed it as such (r. 97549394).

Curiously, I remember testing this after we fixed the BSD Sockets issue in 14.5 (r. 68104781) and it was working as expected. That is, you had to have the multicast entitlement to send broadcasts. So things seem to have regressed since then )-:

If you’re still not seeing the permission alert, I recommend that you remove your app from the device and then re-run it from Xcode. In my experience that’s sufficient to get the system to prompt again, per Local LNP FAQ-13.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I’ve filed it as such (r. 97549394).

I re-tested this on iOS 16 beta [1] and things work as expected there. It seems like the patch came unstuck sometime between 14.5 and 16 beta )-: I’m going to leave it up to iOS engineering to determine if they want to investigate that further and, potentially, fix this in 15. Regardless, I think I’ve explained all the weird behaviour you’re seeing. Reply back here if you see anything else strange.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Specifically iOS 16.0b4 (20A5328h) but I’m not exact version matters.

Possible lack of permission check
 
 
Q