From 5fcf89dff16619bc7ab4652ee644f0777ff32295 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 29 Aug 2017 16:16:04 -0400 Subject: [PATCH 1/5] Ignore "GIF of death." // FREEBIE --- Signal.xcodeproj/project.pbxproj | 14 +- .../TSMessageAdapaters/TSAnimatedAdapter.m | 14 ++ Signal/src/util/NSData+Image.h | 12 ++ Signal/src/util/NSData+Image.m | 139 ++++++++++++++++++ .../OWSOutgoingMessageCollectionViewCell.m | 1 - 5 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 Signal/src/util/NSData+Image.h create mode 100644 Signal/src/util/NSData+Image.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index b4b3acde8..ee6b4a9d6 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ 345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671001E89A5F1006EE662 /* ThreadUtil.m */; }; 3456710A1E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */; }; 346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; }; + 346B66381F55E24900E5122F /* NSData+Image.m in Sources */ = {isa = PBXBuildFile; fileRef = 346B66371F55E24900E5122F /* NSData+Image.m */; }; 3471B1DA1EB7C63600F6AEC8 /* NewNonContactConversationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */; }; 3472229F1EB22FFE00E53955 /* AddToGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3472229E1EB22FFE00E53955 /* AddToGroupViewController.m */; }; 348F2EAE1F0D21BC00D4ECE0 /* DeviceSleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */; }; @@ -445,6 +446,8 @@ 345671081E8A9F5D006EE662 /* TSGenericAttachmentAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGenericAttachmentAdapter.h; sourceTree = ""; }; 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGenericAttachmentAdapter.m; sourceTree = ""; }; 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = ""; }; + 346B66361F55E24900E5122F /* NSData+Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Image.h"; sourceTree = ""; }; + 346B66371F55E24900E5122F /* NSData+Image.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Image.m"; sourceTree = ""; }; 3471B1D81EB7C63600F6AEC8 /* NewNonContactConversationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewNonContactConversationViewController.h; sourceTree = ""; }; 3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewNonContactConversationViewController.m; sourceTree = ""; }; 3472229D1EB22FFE00E53955 /* AddToGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToGroupViewController.h; sourceTree = ""; }; @@ -1422,6 +1425,9 @@ 45E615151E8C590B0018AD52 /* DisplayableTextFilter.swift */, 76EB04EA18170B33006006FC /* FunctionalUtil.h */, 76EB04EB18170B33006006FC /* FunctionalUtil.m */, + 455AC69A1F4F79E500134004 /* ImageCache.swift */, + 346B66361F55E24900E5122F /* NSData+Image.h */, + 346B66371F55E24900E5122F /* NSData+Image.m */, B62F5E0E1C2980B4000D370C /* NSData+ows_StripToken.h */, B62F5E0F1C2980B4000D370C /* NSData+ows_StripToken.m */, 76EB04EC18170B33006006FC /* NumberUtil.h */, @@ -1455,7 +1461,6 @@ 76EB04FB18170B33006006FC /* Util.h */, 45F170D51E315310003FC1F2 /* Weak.swift */, 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */, - 455AC69A1F4F79E500134004 /* ImageCache.swift */, ); path = util; sourceTree = ""; @@ -2236,6 +2241,7 @@ 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */, 4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */, FCFA64B71A24F6730007FB87 /* UIFont+OWS.m in Sources */, + 346B66381F55E24900E5122F /* NSData+Image.m in Sources */, B6B9ECFC198B31BA00C620D3 /* PushManager.m in Sources */, 45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */, 458DE9D91DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m in Sources */, @@ -2580,7 +2586,11 @@ "DEBUG=1", "$(inherited)", ); - "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1"; + "GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = ( + "DEBUG=1", + "$(inherited)", + "SSK_BUILDING_FOR_TESTS=1", + ); GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES; GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; diff --git a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m index 9d2a73787..92b2f3fc9 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m @@ -6,6 +6,7 @@ #import "AttachmentUploadView.h" #import "FLAnimatedImage.h" #import "JSQMediaItem+OWS.h" +#import "NSData+Image.h" #import "TSAttachmentStream.h" #import "UIColor+OWS.h" #import @@ -27,6 +28,8 @@ NS_ASSUME_NONNULL_BEGIN // See comments on OWSMessageMediaAdapter. @property (nonatomic, nullable, weak) id lastPresentingCell; +@property (nonatomic) NSNumber *isImageValid; + @end #pragma mark - @@ -96,10 +99,21 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - JSQMessageMediaData protocol +- (NSNumber *)isImageValid +{ + if (!_isImageValid) { + _isImageValid = @([NSData isValidImageAtPath:[self.attachment mediaURL].path]); + } + return _isImageValid; +} + - (UIView *)mediaView { OWSAssert([NSThread isMainThread]); if (self.cachedImageView == nil) { + if (![self isImageValid]) { + return nil; + } // Use Flipboard FLAnimatedImage library to display gifs NSData *fileData = [NSData dataWithContentsOfURL:[self.attachment mediaURL]]; if (!fileData) { diff --git a/Signal/src/util/NSData+Image.h b/Signal/src/util/NSData+Image.h new file mode 100644 index 000000000..aaf545ac5 --- /dev/null +++ b/Signal/src/util/NSData+Image.h @@ -0,0 +1,12 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import + +@interface NSData (Image) + ++ (BOOL)isValidImageAtPath:(NSString *)filePath; +- (BOOL)isValidImage; + +@end diff --git a/Signal/src/util/NSData+Image.m b/Signal/src/util/NSData+Image.m new file mode 100644 index 000000000..bda89a542 --- /dev/null +++ b/Signal/src/util/NSData+Image.m @@ -0,0 +1,139 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + +#import "NSData+Image.h" + +typedef NS_ENUM(NSInteger, ImageFormat) { + ImageFormat_Unknown, + ImageFormat_Png, + ImageFormat_Gif, + ImageFormat_Tiff, + ImageFormat_Jpeg, + ImageFormat_Bmp, +}; + +@implementation NSData (Image) + ++ (BOOL)isValidImageAtPath:(NSString *)filePath +{ + NSError *error = nil; + NSData *data = [NSData dataWithContentsOfFile:filePath options:NSMappedRead error:&error]; + if (error) { + DDLogError(@"%@ could not read image data: %@", self.tag, error); + } + + return [data isValidImage]; +} + +- (BOOL)isValidImage +{ + // Don't trust the file extension; iOS (e.g. UIKit, Core Graphics) will happily + // load a .gif with a .png file extension. + // + // Instead, use the "magic numbers" in the file data to determine the image format. + ImageFormat imageFormat = [self guessImageFormat]; + if (imageFormat == ImageFormat_Gif) { + return [self hasValidGifSize]; + } else if (imageFormat == ImageFormat_Unknown) { + return NO; + } else { + return YES; + } +} + +- (ImageFormat)guessImageFormat +{ + const NSUInteger kTwoBytesLength = 2; + if (self.length < kTwoBytesLength) { + return ImageFormat_Unknown; + } + + unsigned char bytes[kTwoBytesLength]; + [self getBytes:&bytes range:NSMakeRange(0, kTwoBytesLength)]; + + unsigned char byte0 = bytes[0]; + unsigned char byte1 = bytes[1]; + + if (byte0 == 0x47 && byte1 == 0x49) { + return ImageFormat_Gif; + } else if (byte0 == 0x89 && byte1 == 0x50) { + return ImageFormat_Png; + } else if (byte0 == 0xff && byte1 == 0xd8) { + return ImageFormat_Jpeg; + } else if (byte0 == 0x42 && byte1 == 0x4d) { + return ImageFormat_Bmp; + } else if (byte0 == 0x4D && byte1 == 0x4D) { + // Motorola byte order TIFF + return ImageFormat_Tiff; + } else if (byte0 == 0x49 && byte1 == 0x49) { + // Intel byte order TIFF + return ImageFormat_Tiff; + } + + return ImageFormat_Unknown; +} + ++ (BOOL)areByteArraysEqual:(NSUInteger)length left:(unsigned char *)left right:(unsigned char *)right +{ + for (NSUInteger i = 0; i < length; i++) { + if (left[i] != right[i]) { + return NO; + } + } + return YES; +} + +// Parse the GIF header to prevent the "GIF of death" issue. +// +// See: https://blog.flanker017.me/cve-2017-2416-gif-remote-exec/ +- (BOOL)hasValidGifSize +{ + const NSUInteger kSignatureLength = 3; + const NSUInteger kVersionLength = 3; + const NSUInteger kWidthLength = 2; + const NSUInteger kHeightLength = 2; + const NSUInteger kPrefixLength = kSignatureLength + kVersionLength; + const NSUInteger kBufferLength = kSignatureLength + kVersionLength + kWidthLength + kHeightLength; + + if (self.length < kBufferLength) { + return NO; + } + + unsigned char bytes[kBufferLength]; + [self getBytes:&bytes range:NSMakeRange(0, kBufferLength)]; + + unsigned char kGif87APrefix[kPrefixLength] = { + 0x47, 0x49, 0x46, 0x38, 0x37, 0x61, + }; + unsigned char kGif89APrefix[kPrefixLength] = { + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, + }; + if (![NSData areByteArraysEqual:kPrefixLength left:bytes right:kGif87APrefix] + && ![NSData areByteArraysEqual:kPrefixLength left:bytes right:kGif89APrefix]) { + return NO; + } + NSUInteger width = ((NSUInteger)bytes[kPrefixLength + 0]) | (((NSUInteger)bytes[kPrefixLength + 1] << 8)); + NSUInteger height = ((NSUInteger)bytes[kPrefixLength + 2]) | (((NSUInteger)bytes[kPrefixLength + 3] << 8)); + + // We need to ensure that the image size is "reasonable". + // We impose an arbitrary "very large" limit on image size + // to eliminate harmful values. + const NSUInteger kMaxValidSize = 1 << 18; + + return (width > 0 && width < kMaxValidSize && height > 0 && height < kMaxValidSize); +} + +#pragma mark - Logging + ++ (NSString *)tag +{ + return [NSString stringWithFormat:@"[%@]", self.class]; +} + +- (NSString *)tag +{ + return self.class.tag; +} + +@end diff --git a/Signal/src/views/OWSOutgoingMessageCollectionViewCell.m b/Signal/src/views/OWSOutgoingMessageCollectionViewCell.m index 81e3d1d09..d3655602e 100644 --- a/Signal/src/views/OWSOutgoingMessageCollectionViewCell.m +++ b/Signal/src/views/OWSOutgoingMessageCollectionViewCell.m @@ -3,7 +3,6 @@ // #import "OWSOutgoingMessageCollectionViewCell.h" -#import "FLAnimatedImageView.h" #import "OWSExpirationTimerView.h" #import "UIView+OWS.h" #import From 7f15228aba5a835fe69922c0588147b324b1406e Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 29 Aug 2017 16:20:37 -0400 Subject: [PATCH 2/5] Ignore "GIF of death." // FREEBIE --- Signal/src/Signal-Bridging-Header.h | 1 + .../AttachmentApprovalViewController.swift | 7 +++- .../ViewControllers/FullImageViewController.m | 33 +++++++++++-------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index 960c613b2..bf282a607 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -10,6 +10,7 @@ #import "Environment.h" #import "FLAnimatedImage.h" #import "FingerprintViewController.h" +#import "NSData+Image.h" #import "NotificationsManager.h" #import "OWSAnyTouchGestureRecognizer.h" #import "OWSAudioAttachmentPlayer.h" diff --git a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift index 20d843daf..df56a41f8 100644 --- a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift +++ b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift @@ -171,8 +171,13 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla } private func createAnimatedPreview(attachmentPreviewView: UIView) { + let data = attachment.data + guard (data as NSData).isValidImage() else { + return + } + // Use Flipboard FLAnimatedImage library to display gifs - guard let animatedImage = FLAnimatedImage(gifData:attachment.data) else { + guard let animatedImage = FLAnimatedImage(gifData:data) else { createGenericPreview(attachmentPreviewView:attachmentPreviewView) return } diff --git a/Signal/src/ViewControllers/FullImageViewController.m b/Signal/src/ViewControllers/FullImageViewController.m index 8f87b34e7..9f5c74b29 100644 --- a/Signal/src/ViewControllers/FullImageViewController.m +++ b/Signal/src/ViewControllers/FullImageViewController.m @@ -2,15 +2,16 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // -#import "FLAnimatedImage.h" #import "FullImageViewController.h" -#import "UIUtil.h" -#import "UIView+OWS.h" -#import "TSPhotoAdapter.h" -#import "TSMessageAdapter.h" +#import "AttachmentSharing.h" +#import "FLAnimatedImage.h" +#import "NSData+Image.h" #import "TSAnimatedAdapter.h" +#import "TSMessageAdapter.h" +#import "TSPhotoAdapter.h" #import "UIColor+OWS.h" -#import "AttachmentSharing.h" +#import "UIUtil.h" +#import "UIView+OWS.h" NS_ASSUME_NONNULL_BEGIN @@ -168,14 +169,18 @@ NS_ASSUME_NONNULL_BEGIN - (void)initializeImageView { if (self.isAnimated) { - // Present the animated image using Flipboard/FLAnimatedImage - FLAnimatedImage *animatedGif = [FLAnimatedImage animatedImageWithGIFData:self.fileData]; - FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init]; - imageView.animatedImage = animatedGif; - imageView.frame = self.originRect; - imageView.contentMode = UIViewContentModeScaleAspectFill; - imageView.clipsToBounds = YES; - self.imageView = imageView; + if ([self.fileData isValidImage]) { + // Present the animated image using Flipboard/FLAnimatedImage + FLAnimatedImage *animatedGif = [FLAnimatedImage animatedImageWithGIFData:self.fileData]; + FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init]; + imageView.animatedImage = animatedGif; + imageView.frame = self.originRect; + imageView.contentMode = UIViewContentModeScaleAspectFill; + imageView.clipsToBounds = YES; + self.imageView = imageView; + } else { + self.imageView = [[UIImageView alloc] initWithFrame:self.originRect]; + } } else { // Present the static image using standard UIImageView self.imageView = [[UIImageView alloc] initWithFrame:self.originRect]; From ef21c6d50c9555482796da8e19e53cff847219fd Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 29 Aug 2017 16:25:08 -0400 Subject: [PATCH 3/5] Ignore "GIF of death." // FREEBIE --- Signal/src/util/NSData+Image.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Signal/src/util/NSData+Image.m b/Signal/src/util/NSData+Image.m index bda89a542..d3c5ff178 100644 --- a/Signal/src/util/NSData+Image.m +++ b/Signal/src/util/NSData+Image.m @@ -87,6 +87,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) { // Parse the GIF header to prevent the "GIF of death" issue. // // See: https://blog.flanker017.me/cve-2017-2416-gif-remote-exec/ +// See: https://www.w3.org/Graphics/GIF/spec-gif89a.txt - (BOOL)hasValidGifSize { const NSUInteger kSignatureLength = 3; From cc048b39711b313d50735cfc736acaad75f4de1a Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 30 Aug 2017 09:58:02 -0400 Subject: [PATCH 4/5] Respond to CR. // FREEBIE --- Signal.xcodeproj/project.pbxproj | 6 ---- .../TSMessageAdapaters/JSQMediaItem+OWS.m | 5 +++ .../TSMessageAdapaters/TSAnimatedAdapter.m | 18 +++------- Signal/src/Profiles/OWSProfileManager.m | 7 +++- Signal/src/Signal-Bridging-Header.h | 2 +- .../AttachmentApprovalViewController.swift | 2 +- .../ViewControllers/FullImageViewController.m | 4 +-- Signal/src/util/UIUtil.h | 5 ++- .../Messages/Attachments/TSAttachmentStream.m | 7 ++++ SignalServiceKit/src/Util/MIMETypeUtil.m | 1 - .../src/Util}/NSData+Image.h | 4 +-- .../src/Util}/NSData+Image.m | 20 +++++------ .../src/Util/UIImage+contentTypes.h | 16 --------- .../src/Util/UIImage+contentTypes.m | 34 ------------------- 14 files changed, 42 insertions(+), 89 deletions(-) rename {Signal/src/util => SignalServiceKit/src/Util}/NSData+Image.h (64%) rename {Signal/src/util => SignalServiceKit/src/Util}/NSData+Image.m (86%) delete mode 100644 SignalServiceKit/src/Util/UIImage+contentTypes.h delete mode 100644 SignalServiceKit/src/Util/UIImage+contentTypes.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index ee6b4a9d6..ac4c1d9a7 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -34,7 +34,6 @@ 345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671001E89A5F1006EE662 /* ThreadUtil.m */; }; 3456710A1E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */; }; 346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; }; - 346B66381F55E24900E5122F /* NSData+Image.m in Sources */ = {isa = PBXBuildFile; fileRef = 346B66371F55E24900E5122F /* NSData+Image.m */; }; 3471B1DA1EB7C63600F6AEC8 /* NewNonContactConversationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */; }; 3472229F1EB22FFE00E53955 /* AddToGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3472229E1EB22FFE00E53955 /* AddToGroupViewController.m */; }; 348F2EAE1F0D21BC00D4ECE0 /* DeviceSleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */; }; @@ -446,8 +445,6 @@ 345671081E8A9F5D006EE662 /* TSGenericAttachmentAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGenericAttachmentAdapter.h; sourceTree = ""; }; 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGenericAttachmentAdapter.m; sourceTree = ""; }; 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = ""; }; - 346B66361F55E24900E5122F /* NSData+Image.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Image.h"; sourceTree = ""; }; - 346B66371F55E24900E5122F /* NSData+Image.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Image.m"; sourceTree = ""; }; 3471B1D81EB7C63600F6AEC8 /* NewNonContactConversationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewNonContactConversationViewController.h; sourceTree = ""; }; 3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewNonContactConversationViewController.m; sourceTree = ""; }; 3472229D1EB22FFE00E53955 /* AddToGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToGroupViewController.h; sourceTree = ""; }; @@ -1426,8 +1423,6 @@ 76EB04EA18170B33006006FC /* FunctionalUtil.h */, 76EB04EB18170B33006006FC /* FunctionalUtil.m */, 455AC69A1F4F79E500134004 /* ImageCache.swift */, - 346B66361F55E24900E5122F /* NSData+Image.h */, - 346B66371F55E24900E5122F /* NSData+Image.m */, B62F5E0E1C2980B4000D370C /* NSData+ows_StripToken.h */, B62F5E0F1C2980B4000D370C /* NSData+ows_StripToken.m */, 76EB04EC18170B33006006FC /* NumberUtil.h */, @@ -2241,7 +2236,6 @@ 45794E861E00620000066731 /* CallUIAdapter.swift in Sources */, 4585C4681ED8F8D200896AEA /* SafetyNumberConfirmationAlert.swift in Sources */, FCFA64B71A24F6730007FB87 /* UIFont+OWS.m in Sources */, - 346B66381F55E24900E5122F /* NSData+Image.m in Sources */, B6B9ECFC198B31BA00C620D3 /* PushManager.m in Sources */, 45DF5DF21DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift in Sources */, 458DE9D91DEE7B360071BB03 /* OWSWebRTCDataProtos.pb.m in Sources */, diff --git a/Signal/src/Models/TSMessageAdapaters/JSQMediaItem+OWS.m b/Signal/src/Models/TSMessageAdapaters/JSQMediaItem+OWS.m index da6ff7cfe..2f797ae1c 100644 --- a/Signal/src/Models/TSMessageAdapaters/JSQMediaItem+OWS.m +++ b/Signal/src/Models/TSMessageAdapaters/JSQMediaItem+OWS.m @@ -6,6 +6,7 @@ #import "NumberUtil.h" #import "UIDevice+TSHardwareVersion.h" #import +#import @implementation JSQMediaItem (OWS) @@ -42,6 +43,10 @@ { OWSAssert(imageURL); + if (![NSData ows_isValidImageAtPath:imageURL.path]) { + return CGSizeZero; + } + // With CGImageSource we avoid loading the whole image into memory. CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)imageURL, NULL); if (!source) { diff --git a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m index 92b2f3fc9..35d2204fa 100644 --- a/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m +++ b/Signal/src/Models/TSMessageAdapaters/TSAnimatedAdapter.m @@ -6,13 +6,13 @@ #import "AttachmentUploadView.h" #import "FLAnimatedImage.h" #import "JSQMediaItem+OWS.h" -#import "NSData+Image.h" #import "TSAttachmentStream.h" #import "UIColor+OWS.h" #import #import #import #import +#import NS_ASSUME_NONNULL_BEGIN @@ -28,8 +28,6 @@ NS_ASSUME_NONNULL_BEGIN // See comments on OWSMessageMediaAdapter. @property (nonatomic, nullable, weak) id lastPresentingCell; -@property (nonatomic) NSNumber *isImageValid; - @end #pragma mark - @@ -99,21 +97,10 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - JSQMessageMediaData protocol -- (NSNumber *)isImageValid -{ - if (!_isImageValid) { - _isImageValid = @([NSData isValidImageAtPath:[self.attachment mediaURL].path]); - } - return _isImageValid; -} - - (UIView *)mediaView { OWSAssert([NSThread isMainThread]); if (self.cachedImageView == nil) { - if (![self isImageValid]) { - return nil; - } // Use Flipboard FLAnimatedImage library to display gifs NSData *fileData = [NSData dataWithContentsOfURL:[self.attachment mediaURL]]; if (!fileData) { @@ -122,6 +109,9 @@ NS_ASSUME_NONNULL_BEGIN view.backgroundColor = [UIColor colorWithWhite:0.85f alpha:1.f]; return view; } + if (![fileData ows_isValidImage]) { + return nil; + } FLAnimatedImage *animatedGif = [FLAnimatedImage animatedImageWithGIFData:fileData]; FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init]; imageView.animatedImage = animatedGif; diff --git a/Signal/src/Profiles/OWSProfileManager.m b/Signal/src/Profiles/OWSProfileManager.m index 605ca79b8..5ef2fac70 100644 --- a/Signal/src/Profiles/OWSProfileManager.m +++ b/Signal/src/Profiles/OWSProfileManager.m @@ -7,6 +7,7 @@ #import "NSString+OWS.h" #import "Signal-Swift.h" #import +#import #import #import #import @@ -1333,7 +1334,11 @@ const NSUInteger kOWSProfileManager_MaxAvatarDiameter = 640; { OWSAssert(filename.length > 0); - UIImage *_Nullable image = [UIImage imageWithData:[self loadProfileDataWithFilename:filename]]; + NSData *data = [self loadProfileDataWithFilename:filename]; + if (![data ows_isValidImage]) { + return nil; + } + UIImage *_Nullable image = [UIImage imageWithData:data]; return image; } diff --git a/Signal/src/Signal-Bridging-Header.h b/Signal/src/Signal-Bridging-Header.h index bf282a607..4eecc6337 100644 --- a/Signal/src/Signal-Bridging-Header.h +++ b/Signal/src/Signal-Bridging-Header.h @@ -10,7 +10,6 @@ #import "Environment.h" #import "FLAnimatedImage.h" #import "FingerprintViewController.h" -#import "NSData+Image.h" #import "NotificationsManager.h" #import "OWSAnyTouchGestureRecognizer.h" #import "OWSAudioAttachmentPlayer.h" @@ -49,6 +48,7 @@ #import #import #import +#import #import #import #import diff --git a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift index df56a41f8..45441c887 100644 --- a/Signal/src/ViewControllers/AttachmentApprovalViewController.swift +++ b/Signal/src/ViewControllers/AttachmentApprovalViewController.swift @@ -172,7 +172,7 @@ class AttachmentApprovalViewController: OWSViewController, OWSAudioAttachmentPla private func createAnimatedPreview(attachmentPreviewView: UIView) { let data = attachment.data - guard (data as NSData).isValidImage() else { + guard (data as NSData).ows_isValidImage() else { return } diff --git a/Signal/src/ViewControllers/FullImageViewController.m b/Signal/src/ViewControllers/FullImageViewController.m index 9f5c74b29..cc83f00d2 100644 --- a/Signal/src/ViewControllers/FullImageViewController.m +++ b/Signal/src/ViewControllers/FullImageViewController.m @@ -5,13 +5,13 @@ #import "FullImageViewController.h" #import "AttachmentSharing.h" #import "FLAnimatedImage.h" -#import "NSData+Image.h" #import "TSAnimatedAdapter.h" #import "TSMessageAdapter.h" #import "TSPhotoAdapter.h" #import "UIColor+OWS.h" #import "UIUtil.h" #import "UIView+OWS.h" +#import NS_ASSUME_NONNULL_BEGIN @@ -169,7 +169,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)initializeImageView { if (self.isAnimated) { - if ([self.fileData isValidImage]) { + if ([self.fileData ows_isValidImage]) { // Present the animated image using Flipboard/FLAnimatedImage FLAnimatedImage *animatedGif = [FLAnimatedImage animatedImageWithGIFData:self.fileData]; FLAnimatedImageView *imageView = [[FLAnimatedImageView alloc] init]; diff --git a/Signal/src/util/UIUtil.h b/Signal/src/util/UIUtil.h index 7f001a8ae..d2aea1262 100644 --- a/Signal/src/util/UIUtil.h +++ b/Signal/src/util/UIUtil.h @@ -1,9 +1,12 @@ +// +// Copyright (c) 2017 Open Whisper Systems. All rights reserved. +// + #import #import "MIMETypeUtil.h" #import "UIColor+OWS.h" #import "UIFont+OWS.h" -#import "UIImage+contentTypes.h" #import "UIImage+normalizeImage.h" typedef void (^completionBlock)(void); diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index 74bc26eda..f008b7957 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -4,6 +4,7 @@ #import "TSAttachmentStream.h" #import "MIMETypeUtil.h" +#import "NSData+Image.h" #import "TSAttachmentPointer.h" #import #import @@ -257,6 +258,9 @@ NS_ASSUME_NONNULL_BEGIN if (!mediaUrl) { return nil; } + if (![NSData ows_isValidImageAtPath:mediaUrl.path]) { + return nil; + } return [UIImage imageWithData:[NSData dataWithContentsOfURL:mediaUrl]]; } else { return nil; @@ -314,6 +318,9 @@ NS_ASSUME_NONNULL_BEGIN if (!mediaUrl) { return CGSizeZero; } + if (![NSData ows_isValidImageAtPath:mediaUrl.path]) { + return CGSizeZero; + } // With CGImageSource we avoid loading the whole image into memory. CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)mediaUrl, NULL); diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.m b/SignalServiceKit/src/Util/MIMETypeUtil.m index e3f811dcd..4bcbe3ffc 100644 --- a/SignalServiceKit/src/Util/MIMETypeUtil.m +++ b/SignalServiceKit/src/Util/MIMETypeUtil.m @@ -4,7 +4,6 @@ #import "MIMETypeUtil.h" #if TARGET_OS_IPHONE -#import "UIImage+contentTypes.h" #import #else #import diff --git a/Signal/src/util/NSData+Image.h b/SignalServiceKit/src/Util/NSData+Image.h similarity index 64% rename from Signal/src/util/NSData+Image.h rename to SignalServiceKit/src/Util/NSData+Image.h index aaf545ac5..451f90465 100644 --- a/Signal/src/util/NSData+Image.h +++ b/SignalServiceKit/src/Util/NSData+Image.h @@ -6,7 +6,7 @@ @interface NSData (Image) -+ (BOOL)isValidImageAtPath:(NSString *)filePath; -- (BOOL)isValidImage; ++ (BOOL)ows_isValidImageAtPath:(NSString *)filePath; +- (BOOL)ows_isValidImage; @end diff --git a/Signal/src/util/NSData+Image.m b/SignalServiceKit/src/Util/NSData+Image.m similarity index 86% rename from Signal/src/util/NSData+Image.m rename to SignalServiceKit/src/Util/NSData+Image.m index d3c5ff178..7cd66c21c 100644 --- a/Signal/src/util/NSData+Image.m +++ b/SignalServiceKit/src/Util/NSData+Image.m @@ -15,7 +15,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) { @implementation NSData (Image) -+ (BOOL)isValidImageAtPath:(NSString *)filePath ++ (BOOL)ows_isValidImageAtPath:(NSString *)filePath { NSError *error = nil; NSData *data = [NSData dataWithContentsOfFile:filePath options:NSMappedRead error:&error]; @@ -23,18 +23,18 @@ typedef NS_ENUM(NSInteger, ImageFormat) { DDLogError(@"%@ could not read image data: %@", self.tag, error); } - return [data isValidImage]; + return [data ows_isValidImage]; } -- (BOOL)isValidImage +- (BOOL)ows_isValidImage { // Don't trust the file extension; iOS (e.g. UIKit, Core Graphics) will happily // load a .gif with a .png file extension. // // Instead, use the "magic numbers" in the file data to determine the image format. - ImageFormat imageFormat = [self guessImageFormat]; + ImageFormat imageFormat = [self ows_guessImageFormat]; if (imageFormat == ImageFormat_Gif) { - return [self hasValidGifSize]; + return [self ows_hasValidGifSize]; } else if (imageFormat == ImageFormat_Unknown) { return NO; } else { @@ -42,7 +42,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) { } } -- (ImageFormat)guessImageFormat +- (ImageFormat)ows_guessImageFormat { const NSUInteger kTwoBytesLength = 2; if (self.length < kTwoBytesLength) { @@ -74,7 +74,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) { return ImageFormat_Unknown; } -+ (BOOL)areByteArraysEqual:(NSUInteger)length left:(unsigned char *)left right:(unsigned char *)right ++ (BOOL)ows_areByteArraysEqual:(NSUInteger)length left:(unsigned char *)left right:(unsigned char *)right { for (NSUInteger i = 0; i < length; i++) { if (left[i] != right[i]) { @@ -88,7 +88,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) { // // See: https://blog.flanker017.me/cve-2017-2416-gif-remote-exec/ // See: https://www.w3.org/Graphics/GIF/spec-gif89a.txt -- (BOOL)hasValidGifSize +- (BOOL)ows_hasValidGifSize { const NSUInteger kSignatureLength = 3; const NSUInteger kVersionLength = 3; @@ -110,8 +110,8 @@ typedef NS_ENUM(NSInteger, ImageFormat) { unsigned char kGif89APrefix[kPrefixLength] = { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, }; - if (![NSData areByteArraysEqual:kPrefixLength left:bytes right:kGif87APrefix] - && ![NSData areByteArraysEqual:kPrefixLength left:bytes right:kGif89APrefix]) { + if (![NSData ows_areByteArraysEqual:kPrefixLength left:bytes right:kGif87APrefix] + && ![NSData ows_areByteArraysEqual:kPrefixLength left:bytes right:kGif89APrefix]) { return NO; } NSUInteger width = ((NSUInteger)bytes[kPrefixLength + 0]) | (((NSUInteger)bytes[kPrefixLength + 1] << 8)); diff --git a/SignalServiceKit/src/Util/UIImage+contentTypes.h b/SignalServiceKit/src/Util/UIImage+contentTypes.h deleted file mode 100644 index 6c56cb4cf..000000000 --- a/SignalServiceKit/src/Util/UIImage+contentTypes.h +++ /dev/null @@ -1,16 +0,0 @@ -// -// UIImage+contentTypes.h -// Signal -// -// Created by Frederic Jacobs on 21/12/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. -// - -#import - -@interface UIImage (contentTypes) - -- (NSString *)contentType; -- (BOOL)isSupportedImageType; - -@end diff --git a/SignalServiceKit/src/Util/UIImage+contentTypes.m b/SignalServiceKit/src/Util/UIImage+contentTypes.m deleted file mode 100644 index d52bb450a..000000000 --- a/SignalServiceKit/src/Util/UIImage+contentTypes.m +++ /dev/null @@ -1,34 +0,0 @@ -// Created by Frederic Jacobs on 21/12/14. -// Copyright (c) 2014 Open Whisper Systems. All rights reserved. - -#import "MIMETypeUtil.h" -#import "UIImage+contentTypes.h" - -@implementation UIImage (contentTypes) - -- (NSString *)contentType { - uint8_t c; - [UIImagePNGRepresentation(self) getBytes:&c length:1]; - - switch (c) { - case 0xFF: - return @"image/jpeg"; - case 0x89: - return OWSMimeTypeImagePng; - case 0x47: - return @"image/gif"; - case 0x49: - break; - case 0x42: - return @"image/bmp"; - case 0x4D: - return @"image/tiff"; - } - return nil; -} - -- (BOOL)isSupportedImageType { - return ([self contentType] ? YES : NO); -} - -@end From bb1681f965f610e3f84c9808c1941c99c43c4368 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 30 Aug 2017 12:53:02 -0400 Subject: [PATCH 5/5] Respond to CR. // FREEBIE --- .../src/Messages/Attachments/TSAttachmentStream.m | 5 +++-- SignalServiceKit/src/Util/NSData+Image.m | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m index f008b7957..730138a0f 100644 --- a/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m +++ b/SignalServiceKit/src/Messages/Attachments/TSAttachmentStream.m @@ -258,10 +258,11 @@ NS_ASSUME_NONNULL_BEGIN if (!mediaUrl) { return nil; } - if (![NSData ows_isValidImageAtPath:mediaUrl.path]) { + NSData *data = [NSData dataWithContentsOfURL:mediaUrl]; + if (![data ows_isValidImage]) { return nil; } - return [UIImage imageWithData:[NSData dataWithContentsOfURL:mediaUrl]]; + return [UIImage imageWithData:data]; } else { return nil; } diff --git a/SignalServiceKit/src/Util/NSData+Image.m b/SignalServiceKit/src/Util/NSData+Image.m index 7cd66c21c..987f54ee1 100644 --- a/SignalServiceKit/src/Util/NSData+Image.m +++ b/SignalServiceKit/src/Util/NSData+Image.m @@ -18,7 +18,7 @@ typedef NS_ENUM(NSInteger, ImageFormat) { + (BOOL)ows_isValidImageAtPath:(NSString *)filePath { NSError *error = nil; - NSData *data = [NSData dataWithContentsOfFile:filePath options:NSMappedRead error:&error]; + NSData *data = [NSData dataWithContentsOfFile:filePath options:NSDataReadingMappedIfSafe error:&error]; if (error) { DDLogError(@"%@ could not read image data: %@", self.tag, error); }