objective-cnssecurecoding

How to use NSSecureCoding with id objects


I'm creating a linked list and using containers to group the object, next, and previous properties. Like Foundation collections, I'd like it to implement NSSecureCoding. Here's the declaration:

@interface ListContainer : NSObject <NSCopying, NSSecureCoding>

@property (readonly, nonatomic) id object;
@property (nonatomic) ListContainer * next;
@property (nonatomic) ListContainer * previous;

@end

When implementing the - initWithCoder: method it hit me that I don't know what class to use for the object:

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];

    if (self) {

        _object = [aDecoder decodeObjectOfClass:<#(__unsafe_unretained Class)#> forKey:@"object"];

        BOOL nextIsNil = [aDecoder decodeBoolForKey:@"nextIsNil"];

        if (!nextIsNil) {

            // Decode next
            _next = [aDecoder decodeObjectOfClass:[ListContainer class] forKey:@"next"];

            if (_next == nil) {
                return nil;
            }

            // Link the nodes manually to prevent infinite recursion
            self.next.previous = self;
        }
    }

    return self;
}

Should I use -decodeObjectForKey: instead? Is it still secure coding?


Solution

  • I ended up posting the same question to Cocoa's mailing list and the most interesting discussion happened. Some of the highlights:

    [...] Make an NSArray of normal stuff, like NSString, NSNumber, encode it, decode it with decodeObjectForClasses, with no classes. You’ll fail on the array. Add the NSArray to the list of allowed classes and .. it works. So, you think, NSArray will blindly decode anything so it’s no-longer secure.

    Add an object of a custom class which implements secure coding into the array, and it will start failing again. NSArray, and the other collection types, allow elements of known secure system types, like NSString, but fail at anything outside that. [...]

    At this point I understand that NSArray doesn't behave as I expected. Secure coding doesn't seem so secure anymore:

    This seems far from ideal [...] The fact that it decodes a set of classes known to implement NSSecureCoding is wrong, IMO, for two reasons [...]

    1) The fact that the contained class implements NSSecureCoding does not mean that I'm expecting it. [...]

    2) It limits the classes which can be stored. [...]

    Getting a class that I'm not expecting in a substitution attack is especially dreadful. Apparently Cocoa's promise is different, though:

    [...] if you use NSArray() or other collection classes directly in your coding, you need to check what you got back. They are ‘securely’ decoded to the extent that Apple believes decoding them will not result in a buffer overflow etc, that’s all you get by default. [...]

    So, no, NSSecureCoding does not guarantee secure encoding of containers, or at least it doesn't guarantee type checking and you must do it yourself. Not even in Cocoa's native data structures as I initially assumed (with reason, I still think that).

    Props go to Roland King for all the effort. You can see the full conversation here.