POSIX sendto fails due to Sequoia's new LAN Privacy & Security permission request

PLATFORM AND VERSION iOS

Development environment: Xcode Version 16.0 (16A242d), macOS 15.0 (24A335)

Run-time configuration: macOS 15.0 (24A335)

DESCRIPTION OF PROBLEM

macOS Sequoia has new Privacy & Security requirements for local area network access. This causes a call to POSIX socket function 'sendto' to fail immediately with EHOSTUNREACH on the first execution of any app that calls it. That failure occurs even if the socket is set to block for well over the time that it would typically take for the user to click "Allow" when presented with a system dialogue box that requests new permissions for the app.

A test XCode project has been provided that is capable of reproducing the issue (see macOSsocketfail.zip at https://drive.google.com/file/d/14VxkT03ddm48RCXikLHf-aWgdqxwnpAB/view?usp=sharing). It will generate a log file that contains time-stamped messages. They report each step in the creation of a blocking UDP socket – and which system call has failed as a result of which error. The message time stamps demonstrate that macOS has terminated the sendto function call after tens of microseconds, well before the 5 minute timeout on the socket, and that it terminates with errno EHOSTUNREACH. That error is misleading, because the destination is pingable on my setup at the time of execution. The second execution of the app functions without error if "Allow" has been selected during the first run.

This specific macOS behaviour does not appear to be documented anywhere that I have yet encountered; e.g. the sendto man page, the Privacy & Security LAN FAQ, etc. It is, however, highly disruptive to the use of our product, which hinges on LAN access. We have a situation in which a relatively large collection of apps are using the same shared library to manage network access. All of them must now receive manual permission from an administrator to work; but all of them will fail on their first execution. The problem is amplified because our customers use our framework to build their own apps, and not every user is an administrator. In contrast, apps that use our framework would simply work without issue on their first execution when run on macOS versions that precede Sequoia.

We must support our software across multiple platforms, hence the reason that we are using POSIX function calls to implement networking. Unfortunately, the use of an Apple-specific networking API is not a viable solution for us.

How should we mitigate this problem? Is there some way to configure an Xcode project so that the build product will already have Sequoia LAN permissions? I have read about the com.apple.developer.networking.multicast entitlement, but it is unclear whether it will help us, from the material that is available.

STEPS TO REPRODUCE

POSIX function call sequence

For the following, addr has type struct sockaddr_in, and it is set appropriately for binding or broadcasting using standard library macros and functions. &addr is cast to a const struct sockaddr pointer and assigned to saddr.

  1. sock = socket( PF_INET, SOCK_DGRAM , 0 ) ;
  2. bind( sock, saddr, sizeof( addr ) ) ;
  3. r = 1 ; setsockopt( sock, SOL_SOCKET, SO_BROADCAST, &r, sizeof( r ) );
  4. struct timeval timeout = { 300 , 0 }; setsockopt( sock , SOL_SOCKET , SO_RCVTIMEO , &timeout , sizeof( timeout ) );
  5. sendto( sock, msg, strlen( msg ) + 1, 0, saddr , sizeof( addr ) ) ;

Test program, presuming that a device with IP 100.1.1.1 exists on the LAN that the mac is also connected to.

  1. Open Xcode project macOSsocketfail.
  2. Build project.
  3. Execute first run of macOSsocketfail.app.
  4. Click "Allow" when asked for LAN permissions.
  5. Search for macOSsocketfail_log.txt, and make a copy with a distinct name e.g. macOSsocketfail_log_firstrun.txt.
  6. Execute second run of macOSsocketfail.app.
  7. Search for macOSsocketfail_log.txt, and make a copy with a distinct name e.g. macOSsocketfail_log_secondrun.txt.
  8. Examine log files.

The first run log file will contain the following messages (time stamping will naturally differ):

1727710614.064009: Running udptest

1727710614.064015: Creating socket

1727710614.064030: Bind socket to port: 4000

1727710614.064061: Enable socket broadcast

1727710614.064064: Set socket timeout to 300.000000sec

1727710614.064067: Attempt to send blocking UDP connection packet to 100.1.1.1:589

1727710614.064124: sendto: No route to host

Error during call to sendto: errno is EHOSTUNREACH: No route to host

The second run log file will record a different outcome:

1727713660.733431: Running udptest

1727713660.733436: Creating socket

1727713660.733451: Bind socket to port: 4000

1727713660.733476: Enable socket broadcast

1727713660.733479: Set socket timeout to 300.000000sec

1727713660.733482: Attempt to send blocking UDP connection packet to 100.1.1.1:589

1727713660.733540: Ran to completion with no error detected

Note that each line of the log files begins with a timestamp. The unit is seconds, and the resolution is to the nearest microsecond. Time values are obtained using gettimeofday().

RELEVANT LINKS

https://developer.apple.com/forums/thread/663858

https://forums.developer.apple.com/forums/thread/757824

https://developer.apple.com/forums/thread/760964

https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_networking_multicast/

https://developer.apple.com/forums/thread/657887

https://developer.apple.com/forums/thread/655920

This issue was mentioned in this WWDC video. It refers you to this older video from WWDC 2020. You already mentioned two of the other links I was going to refer to you.

I tried your app and it said it ran to completion with no error. It doesn't seem like it should work at all since you don't seem to have the proper entitlement and usage string. I thought I had changed the destination IP to be a local IP address. But I was testing in a VM, so maybe the networking is too funky and it was treating it as an internet connection.

You don't need to be administrator to approve local network access.

Obviously, for issues like this, Apple does not provide any way for an app to bypass user consent. That would defeat the purpose. Apple does usually provide some MDM key to preset the value for managed devices.

If this is library code, I don't see how it would be a problem. It's the app developer's responsibility to manage this.

What Etresoft said, plus…

EHOSTUNREACH … is misleading, because the destination is pingable on my setup at the time of execution.

Right. Our hands are tied here because of the limitations of the available errno error codes. In Apple APIs, like Network framework, we can yield more specific errors. See FAQ-10 in the Local Network Privacy FAQ (which I haven’t yet updated for macOS, but is generally applicable here).

We have a situation in which a relatively large collection of apps are using the same shared library to manage network access. All of them must now receive manual permission from an administrator to work; but all of them will fail on their first execution. The problem is amplified because our customers use our framework to build their own apps, and not every user is an administrator.

I’m confused by your comments about an administrator. On macOS, local network privacy is a per-user setting. Each user sets this how they see fit. There’s no need for an admin to get involved.

Note The flip side to this is that there’s no mechanism for an admin to preconfigure this setting. That is, there’s no equivalent to the com.apple.TCC.configuration-profile-policy payload for local network privacy. If you’d like us to add something like that, I encourage you to file an enhancement request describing why it’s important to your setup. And if you file such a bug, please post the number, just for the record.

Unfortunately, the use of an Apple-specific networking API is not a viable solution for us.

The Apple APIs offer better insight into this issue, but they don’t materially change things. We enforce the local network privacy constraints regardless of what API you use. If you could bypass this by switching API, that’d be a serious security bug [1].

I have read about the com.apple.developer.networking.multicast entitlement, but it is unclear whether it will help us, from the material that is available.

It will not. The multicast entitlement is necessary for some specific operations but:

  • User approval is still required.

  • The fact that your program works after receiving user approval is a clear indication that the operations you’re doing don’t need this entitlement.

Share and Enjoy

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

[1] There were times during the introduction of this feature on iOS where that was the case, although ironically it was the reverse to what you’re expecting. That is, we failed to enforce local network privacy for BSD Sockets O-: That’s now been fixed. *phew*

Thanks to @Etresoft & @DTS Engineer for your replies. You are quite right, I'd assumed that administrator authentication would be needed, when it is not; it can be like that with certain other OSs.

@Etresoft, I get a similar outcome in which no permission is sought when I have no active LAN connection. But it is requested after a connection has been enabled. The demo is meant to be a minimalist example – and only the two networking entitlements were required to make it run.

@DTS Engineer, I take it then that our only real option would be to #ifdef __APPLE__ an Apple-specific block into our code that would query a more specific reason for a socket connection failure using e.g. the Networking framework.

There is a follow up question, then, on how to test and debug all of this. The advice is that Privacy & Security permissions will be forgotten when an app is deleted. We do not find this. Permissions are remembered across a cycle of deletion and installation. Commands such as tccutil do not affect LAN permissions. There does not seem to be any way of removing LAN permissions once they have been assigned to an app by a user.

The only approach I have found that seems to work – and it is rather cumbersome – is to create a new user and then run the app from that account every time that I want to test the permissions handling of the app. Surely there is a better way?

POSIX sendto fails due to Sequoia's new LAN Privacy & Security permission request
 
 
Q