write() not working in OS betas for files in App Group, from app and extension

We have an App Group defined in our entitlements file so that two pieces of our software, a management GUI and a VPN extension, can write files to the same location.

This is not just for regular log files. There's data we want to record which isn't appropriate for the system logs.

What we're seeing on the iOS and macOS betas is that the write() file always fails, and we end up with \0s written to the file instead of the data.

This is true both with the shipping versions of our applications on the App Store and with builds made with Xcode 15 and run on the devices in the debugger.

  • Happens from both the Network Extension and the management application.
  • Both macOS and iOS.
  • Shipping apps and freshly built with latest tools.

There's nothing we see in the Console logs that would appear to explain this.

I didn't see anything in the release notes that would address this, but I could easily have missed something.

Anyone else seen this?

Replies

is that the write() file always fails, and we end up with \0s written to the file instead of the data.

You’re talking about the write system call here, yes? If so, these two statements don’t really gel. If write fails, it returns -1 and sets errno. If the file is getting filled with zeroes, the write succeeded, but it just wrote zeroes.

I’m not aware of sandbox or security restriction that’d cause that behaviour. It’s either a bug in the file system side of things, or your code has actually passed a buffer full of zeroes to write. The latter is by far the most likely. I recommend you add some temporary logging to your… well… logging (-: that logs a subset of this data to the system log. You can then compare the system log results with the contents of the file.

Share and Enjoy

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

You’re talking about the write system call here, yes? If so, these two statements don’t really gel. If write fails, it returns -1 and sets errno. If the file is getting filled with zeroes, the write succeeded, but it just wrote zeroes.

And yet it's happening.

  • write() returns -1
  • errno is set to EFAULT
  • file size changes by the number of bytes attempted
  • file is filled with 0s

I’m not aware of sandbox or security restriction that’d cause that behaviour. It’s either a bug in the file system side of things, or your code has actually passed a buffer full of zeroes to write. The latter is by far the most likely.

No, it's not--this happens with the shipping applications, and on earlier versions of the OSes the correct information is written. The only difference is the OS versions. A bug in the betas seems likely, but one question would be why more people aren't seeing it.

I recommend you add some temporary logging to your… well… logging (-: that logs a subset of this data to the system log. You can then compare the system log results with the contents of the file.

We've done that. When we log to both, we see that we're logging valid data.

Kevin

EFAULT is a weird one. The only documented reason to get that error is that the buffer you pass to write is invalid. It’s also a pretty rare error; when I do see it’s usually because folks have a memory management bug, often because they’re failed to follow the (very strict) pointer rules in Swift.

Can you reproduce this with a small test project?

Share and Enjoy

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

I'll see what I can do. My suspicion right now is that it's something related to the app group, because if write() was broken in a general sense there would have been more noise on the forums about it.

My suspicion right now is that it's something related to the app group

I’m still sceptical that this bug is what you think it is. All of the likely candidates here — checking whether you have access to the app group, finding its container, checking whether you have access to a specific file in that container — are done before you open a file descriptor. Once you have a file descriptor open, the write system call does no more security checking [1]. I can’t think of any theories that would explain the behaviour you’re describing.

I'll see what I can do.

Thanks.

Share and Enjoy

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

[1] Because doing security checks there would be really bad for performance.

This is reproducible with the shipping apps, in the App Stores.

No connections needed--as soon as you open the applications there is data written to the files, and the problem can be seen. I've filed two bugs, one for iOS/iPadOS and one for macOS:

iOS: FB12663311 macOS: FB12663328

Add a Comment

I’ve narrowed down the problem.

This code:

    NSString* nstb = [NSString stringWithFormat:@"[%@] <%@>: %@\n",
                      time,
                      tag,
                      info];
    
    char* cstring = [nstb cStringUsingEncoding:NSASCIIStringEncoding];

Always returns a NULL cstring on the betas. On all previous OS versions it returns a valid cstring.

The data in the NSString is all data which can be represented with the ASCII encoding, and the documentation for the function says that it will return NULL only when the data cannot be represented with the requested encoding.

This is a pretty fundamental change in behavior.

The data in the NSString is all data which can be represented with the ASCII encoding

I was caught out a few years ago when the iOS keyboard started automatically converting straight quoutes into curly open- and close-quotes, which are not ASCII. I wonder if something similar is happening to you? Maybe your date-time string has come from a formatter that has changed behaviour?

Is there a reason you can’t convert to UTF-8?

We can convert to UTF8 where we first saw the problem, but we can't send that over the internet...

I checked into contents, and we use NSDateFormatter's stringFromDate to generate the time, with standard setLocale, setDateStyle, and setTimeStyle. It appears that it's generating a lovely ASCII string except for the first space in " PM PDT" at the end of the date string:

e280af 50 4d 20 50 44 54

e280af = sp??
50 = P
4d = M
20 = sp
50 = P
44 = D
54 = T

So that's the root here. Of course we can't use that in any Internet exchange that wants ASCII headers or payload, and an unexpected change in the behavior of NSDateFormatter isn't really cool.

Non-breaking space!

Nice bit of debugging. You’ll be able to tell this story for years to come.

Edited to add: It's actually a narrow non-breaking space. I bet you're not the only person with code that doesn't handle that. I'd be interested to know if there is a way to type it.

with standard setLocale

What do you mean by that? Using the en_US_POSIX locale?

Share and Enjoy

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

Yes.

NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
[dateFormatter setLocale:enUSPOSIXLocale];
[dateFormatter setDateStyle:NSDateFormatterMediumStyle];
[dateFormatter setTimeStyle:NSDateFormatterLongStyle];

NSString* dateString = [dateFormatter stringFromDate:[NSDate date]];

And the string will have a non-ASCII space character. But only on the beta OSes.

Also with Swift, if I put this in a playground:

import Cocoa

var formatter = DateFormatter()
var locale = Locale(identifier: "en_US_POSIX")

formatter.locale = locale
formatter.dateStyle = .medium
formatter.timeStyle = .long

var dateString = formatter.string(from: Date.now)

var data = dateString.data(using: .utf8)

print("\(dateString)")
print("\([UInt8](data!))")

We can see that on a beta OS it's got the non-ASCII space, and on Ventura it's got 0x20.

You should definitely file a bug about that. The formats generated by en_US_POSIX shouldn’t change.

Please post your bug number, just for the record.

Share and Enjoy

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

we use NSDateFormatter's stringFromDate to generate the time

Easy fix then, replace that with something that Apple can’t break.