Highlight
Apple uses five code snippets for path traversal, session state, deserialization whitelisting, buffer length, and integer overflow to illustrate that threat modeling falls on the checkpoint of each external input entering the app.
Core Content
Security issues rarely arise only at the level of the encryption algorithm. A more common situation is that the App treats external input as internal data: file names sent from the remote end, session messages sent from another device, binary fields in CloudKit records, and class collections declared during the deserialization phase, all may become entry points for attacks.
The material in this session breaks down threat modeling into code-level issues. Developers first find out where the data enters the app, and then ask three questions: can this value change the file path, can it push the state machine, will it cause the runtime to create objects that should not be created, and whether the length and quantity are verified before entering the memory operation.
The protection method provided by Apple is also very straightforward. The file name first converges tolastPathComponent;Session messages must match session, state and sender at the same time; dynamic class collection is generated by protocol constraints; fixed size memory is copied before checking type and length; used when calculating bytes from quantityos_mul_overflow. These checks may seem small, but they determine whether an attacker can cross the trust boundary.
Detailed Content
1. Path traversal: Convergence of external file names into a single safe file name
(16:34) A common error in the file import function is to transfer data from the remote endnameDirectly to the back of the local directory. Fragments taken firstlastPathComponent, and then reject empty strings,.and.., and finally write to the temporary directory.
func handleIncomingFile(_ incomingResourceURL: URL, with name: String, from fromID: String) {
guard
case let safeFileName = name.lastPathComponent,
safeFileName.count > 0,
safeFileName != "..", safeFileName != "." else { return }
let destinationFileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(safeFileName)
// Copy the file into a temporary directory
try! FileManager.default.copyItem(at: incomingResourceURL, to: destinationFileURL)
}
Key points:
name.lastPathComponentRemove the directory hierarchy carried in the external input, leaving only the last path component.safeFileName.count > 0Reject empty file names to avoid generating meaningless destination paths.safeFileName != ".."andsafeFileName != "."Reject the two special path components of the current directory and the parent directory.NSTemporaryDirectory()Limit the imported files to the temporary directory, and then passappendingPathComponentSplice checked filenames.
2. Status management: The remote message must match the current status and sender
(22:26) The session invitation acceptance message comes from the remote end and cannot be relied upon solelysessionIdentifierJust switch the connection status to Connected. The code also checks whether the session exists, whether the status is still being invited, and whether the sender is in the invited list.
func handleSessionInviteAccepted(with message: RemoteMessage, from fromID: String) {
guard session = sessionsByIdentifier[message.sessionIdentifier],
session.state == .inviting,
session.invitedFromIdentifiers.contains(fromID) else { return }
session.state = .connected
session.setupSocket(to: fromID) { socket in
cameraController.send(to: socket)
}
}
Key points:
sessionsByIdentifier[message.sessionIdentifier]First confirm that the message refers to a known session.session.state == .invitingLimit the state transition to the legal stage, and old messages cannot advance the state at will.session.invitedFromIdentifiers.contains(fromID)Bind the action to the invited sender.session.state = .connectedandsetupSocketOnly executed after all three checks have been passed.
3. Deserialization whitelist: dynamic collections must also be generated from explicit rules
(30:56) Requires dynamic buildallowedClassesInstead of accepting arbitrary class names, the fragment traverses the runtime class list and only collects classes that conform to a certain protocol. The resulting set still has boundaries.
NSSet *classesWhichConformToProtocol(Protocol *protocol) {
NSMutableSet *conformingClasses = [NSMutableSet set];
unsigned int classesCount = 0;
Class *classes = objc_copyClassList(&classesCount);
if (classes != NULL) {
for (unsigned int i = 0; i < classesCount; i++) {
if (class_conformsToProtocol(classes[i], protocol)) {
[conformingClasses addObject: classes[i]];
}
}
free(classes);
}
return conformingClasses;
}
Key points:
objc_copyClassListGet the list of classes in the current process from the Objective-C runtime.class_conformsToProtocol(classes[i], protocol)It is a whitelist condition. Only classes declared to comply with the protocol will enter the collection.free(classes)Release the class array returned by runtime to avoid memory leaks introduced by auxiliary checking code.- The return value is a
NSSet, suitable as a set of allowed classes in the subsequent secure decoding process.
4. Buffer overflow: verify type and length before copying fixed-length fields
(34:23) Fields in CloudKit records come from external storage. BundleNSDataCopy to fixed size_uuidBefore doing so, the fragment first confirms that the field exists, is of the correct type, and has a length equal to the target buffer size.
@implementation
- (BOOL)unpackTeaClubRecord:(CKRecord *)record {
...
NSData *data = [record objectForKey:@"uuid"];
if (data == nil ||
![data isKindOfClass:[NSData class]] ||
data.length != sizeof(_uuid)) {
return NO;
}
memcpy(&_uuid, data.bytes, data.length);
...
Key points:
data == nilMissing fields are rejected, and subsequent memory operations will not face empty objects.isKindOfClass:[NSData class]Confirm the field type to avoid treating other objects as binary buffers.data.length != sizeof(_uuid)Bind the input length to the target buffer size.memcpyExecuted only after length check, number of bytes copied from verifieddata.length。
5. Integer overflow: safely calculate the number of bytes before creating the string
(36:06) The string length field also comes from the record. The fragment is not used directlycount * sizeof(unichar), but useos_mul_overflowCount the number of bytes and putNSData.lengthInitialize the string after aligning it with the calculation result.
@implementation
- (BOOL)unpackTeaClubRecord:(CKRecord *)record {
...
NSData *name = [record objectForKey:@"name"];
int32_t count = [[record objectForKey:@"nameCount"] unsignedIntegerValue];
int32_t byteCount = 0;
if (name == nil ||
![name isKindOfClass:[NSData class]] ||
os_mul_overflow(count, sizeof(unichar), &byteCount) ||
name.length != byteCount) {
return NO;
}
_name = [[NSString alloc] initWithCharacters:name.bytes
length:count];
...
Key points:
nameandnameCountIt comes from the same record and should be treated as external input before use.os_mul_overflow(count, sizeof(unichar), &byteCount)Put in the multiplication resultbyteCountCheck for overflow before.name.length != byteCountConfirm that the byte buffer length and number of characters are consistent.initWithCharacters:length:Only called after byte count verification to avoid interpreting incomplete buffers as strings.
Core Takeaways
-
Import files into safety net What to do: Unify the file names of AirDrop, document picker, share extensions and download tasks into the app to a cleaning function. Why it’s worth doing: The path traversal snippet explains that the directory hierarchy must be removed and special path components rejected before external file names can be entered into path splicing. How to start: Write one
safeLocalFileNamehelper, override empty string,.、..、../../name, absolute path and long file name testing, and then limit file copying to the temporary directory or App private container. -
Remote message state machine What to do: Draw a state table for collaboration, screencasting, instant messaging, or device interconnection functions, and list the state transitions that each remote message is allowed to trigger. Why it’s worth doing: The session example requires that session exists and the state is
.inviting, the sender is in the invitation list, and the socket cannot be established even if one item is missing. How to start: putstate、messageType、senderIDand current permissions are merged into a guard function, and unit tests cover duplicate messages, old messages and unknown senders. -
Deserialization allows class registry What to do: Put all model classes that are allowed to be decoded into an explicit protocol or registry, and disable call sites from temporarily splicing class name lists. Why it’s worth doing:
classesWhichConformToProtocolFragments constrain dynamic collections with protocols, preserving flexibility while making boundaries auditable. How to start: Define a marker protocol that is only used for safe decoding, let the class that needs to be decoded explicitly declare that it conforms to the protocol, and then generate it at the decoding entryNSSet。 -
CloudKit field unpacking verification layer What to do: for
CKRecordCreate a unified unpacking function for the binary fields, count fields and string fields in it. Why it’s worth doing: Both CloudKit snippets are getting inmemcpyorNSStringCheck before initializationNSDataType, exact length, and multiplication overflow. How to start: First change fields such as UUID and name toreadUUID(record:key:)、readUTF16Name(record:dataKey:countKey:)Small functions like this, and then complete the malformation record test. -
PR Level Threat Modeling Checklist What it does: Each PR that introduces external input answers four questions: will it write the file path, will it advance the state, will it decode the object, and will it copy the memory by length. Why it’s worth doing: The code snippets of this session compress the security review into a few specific entries, which is easier to implement than discussing “security” in general. How to get started: Add these four checks to your PR template and require that the relevant code be accompanied by at least one malicious input test.
Related Sessions
- Build an Endpoint Security app — Continue to look at system-level security event monitoring on macOS, suitable for reading in conjunction with App internal threat modeling.
- Boost performance and security with modern networking — Bring network protocols, TLS 1.3, and encrypted DNS into the same set of security boundary checks.
- Build trust through better privacy — Complete the product layer requirements of security design from the perspective of user trust and privacy minimization.
- Explore logging in Swift — Learn to protect sensitive data when logging events and errors to avoid logs becoming a new entry point for information leakage.
- One-tap account security upgrades — Direct follow-up to account security upgrades, covering Sign in with Apple and iCloud Keychain strong password processes.
Comments
GitHub Issues · utterances