[ObjC] Enforce bytes and string field size limits.
PiperOrigin-RevId: 509863774
diff --git a/objectivec/GPBCodedInputStream.m b/objectivec/GPBCodedInputStream.m
index ad5c75e..9acf6f1 100644
--- a/objectivec/GPBCodedInputStream.m
+++ b/objectivec/GPBCodedInputStream.m
@@ -51,6 +51,10 @@
// int CodedInputStream::default_recursion_limit_ = 100;
static const NSUInteger kDefaultRecursionLimit = 100;
+// Bytes and Strings have a max size of 2GB.
+// https://protobuf.dev/programming-guides/encoding/#cheat-sheet
+static const uint32_t kMaxFieldSize = 0x7fffffff;
+
static void RaiseException(NSInteger code, NSString *reason) {
NSDictionary *errorInfo = nil;
if ([reason length]) {
@@ -223,14 +227,20 @@
}
NSString *GPBCodedInputStreamReadRetainedString(GPBCodedInputStreamState *state) {
- int32_t size = ReadRawVarint32(state);
+ uint64_t size = GPBCodedInputStreamReadUInt64(state);
+ if (size > kMaxFieldSize) {
+ // TODO(thomasvl): Maybe a different error code for this, but adding one is a breaking change
+ // so reuse an existing one.
+ RaiseException(GPBCodedInputStreamErrorInvalidSize, nil);
+ }
+ NSUInteger ns_size = (NSUInteger)size;
NSString *result;
if (size == 0) {
result = @"";
} else {
CheckSize(state, size);
result = [[NSString alloc] initWithBytes:&state->bytes[state->bufferPos]
- length:size
+ length:ns_size
encoding:NSUTF8StringEncoding];
state->bufferPos += size;
if (!result) {
@@ -246,21 +256,31 @@
}
NSData *GPBCodedInputStreamReadRetainedBytes(GPBCodedInputStreamState *state) {
- int32_t size = ReadRawVarint32(state);
- if (size < 0) return nil;
+ uint64_t size = GPBCodedInputStreamReadUInt64(state);
+ if (size > kMaxFieldSize) {
+ // TODO(thomasvl): Maybe a different error code for this, but adding one is a breaking change
+ // so reuse an existing one.
+ RaiseException(GPBCodedInputStreamErrorInvalidSize, nil);
+ }
+ NSUInteger ns_size = (NSUInteger)size;
CheckSize(state, size);
- NSData *result = [[NSData alloc] initWithBytes:state->bytes + state->bufferPos length:size];
+ NSData *result = [[NSData alloc] initWithBytes:state->bytes + state->bufferPos length:ns_size];
state->bufferPos += size;
return result;
}
NSData *GPBCodedInputStreamReadRetainedBytesNoCopy(GPBCodedInputStreamState *state) {
- int32_t size = ReadRawVarint32(state);
- if (size < 0) return nil;
+ uint64_t size = GPBCodedInputStreamReadUInt64(state);
+ if (size > kMaxFieldSize) {
+ // TODO(thomasvl): Maybe a different error code for this, but adding one is a breaking change
+ // so reuse an existing one.
+ RaiseException(GPBCodedInputStreamErrorInvalidSize, nil);
+ }
+ NSUInteger ns_size = (NSUInteger)size;
CheckSize(state, size);
// Cast is safe because freeWhenDone is NO.
NSData *result = [[NSData alloc] initWithBytesNoCopy:(void *)(state->bytes + state->bufferPos)
- length:size
+ length:ns_size
freeWhenDone:NO];
state->bufferPos += size;
return result;
diff --git a/objectivec/GPBMessage.m b/objectivec/GPBMessage.m
index f414570..a06bb6f 100644
--- a/objectivec/GPBMessage.m
+++ b/objectivec/GPBMessage.m
@@ -2003,9 +2003,6 @@
return;
}
NSData *data = GPBCodedInputStreamReadRetainedBytesNoCopy(state);
- if (data == nil) {
- return;
- }
[self mergeFromData:data extensionRegistry:extensionRegistry];
[data release];
}
diff --git a/objectivec/Tests/GPBCodedInputStreamTests.m b/objectivec/Tests/GPBCodedInputStreamTests.m
index 0504062..0d0d327 100644
--- a/objectivec/Tests/GPBCodedInputStreamTests.m
+++ b/objectivec/Tests/GPBCodedInputStreamTests.m
@@ -28,6 +28,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#import <Foundation/Foundation.h>
#import "GPBTestUtilities.h"
#import "GPBCodedInputStream.h"
@@ -370,10 +371,23 @@
@"should throw a GPBCodedInputStreamException exception ");
}
-- (void)testBytesWithNegativeSize {
- NSData* data = bytes(0xFF, 0xFF, 0xFF, 0xFF, 0x0F);
+- (void)testBytesOver2GB {
+ NSData* data = bytes(0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x01, 0x02, 0x03); // don't need all the bytes
GPBCodedInputStream* input = [GPBCodedInputStream streamWithData:data];
- XCTAssertNil([input readBytes]);
+ @try {
+ __unused NSData* result = [input readBytes];
+ XCTFail(@"Should have thrown");
+ } @catch (NSException* anException) {
+ // Ensure the correct error within the exception.
+ XCTAssertTrue([anException isKindOfClass:[NSException class]]);
+ XCTAssertEqualObjects(anException.name, GPBCodedInputStreamException);
+ NSDictionary* userInfo = anException.userInfo;
+ XCTAssertNotNil(userInfo);
+ NSError* err = userInfo[GPBCodedInputStreamUnderlyingErrorKey];
+ XCTAssertNotNil(err);
+ XCTAssertEqualObjects(err.domain, GPBCodedInputStreamErrorDomain);
+ XCTAssertEqual(err.code, GPBCodedInputStreamErrorInvalidSize);
+ }
}
// Verifies fix for b/10315336.
diff --git a/objectivec/Tests/GPBMessageTests+Serialization.m b/objectivec/Tests/GPBMessageTests+Serialization.m
index 3c2381c..5a7dc16 100644
--- a/objectivec/Tests/GPBMessageTests+Serialization.m
+++ b/objectivec/Tests/GPBMessageTests+Serialization.m
@@ -1266,12 +1266,17 @@
XCTAssertEqual(error.code, GPBCodedInputStreamErrorRecursionDepthExceeded);
}
-- (void)testParseDelimitedDataWithNegativeSize {
- NSData *data = DataFromCStr("\xFF\xFF\xFF\xFF\x0F");
+- (void)testParseDelimitedDataOver2GB {
+ NSData *data = DataFromCStr("\xFF\xFF\xFF\xFF\x0F\x01\x02\0x3"); // Don't need all the bytes
GPBCodedInputStream *input = [GPBCodedInputStream streamWithData:data];
NSError *error;
- [GPBMessage parseDelimitedFromCodedInputStream:input extensionRegistry:nil error:&error];
- XCTAssertNil(error);
+ GPBMessage *result = [GPBMessage parseDelimitedFromCodedInputStream:input
+ extensionRegistry:nil
+ error:&error];
+ XCTAssertNil(result);
+ XCTAssertNotNil(error);
+ XCTAssertEqualObjects(error.domain, GPBCodedInputStreamErrorDomain);
+ XCTAssertEqual(error.code, GPBCodedInputStreamErrorInvalidSize);
}
#ifdef DEBUG