crashpad/util/net/http_transport_mac.mm
Mark Mentovai f496130fd5 HTTPTransportMac: CFStream Read() must always set at_eof
CFStream’s CFReadStreamGetBuffer() calls the Read() callback without
initializing at_eof. The callback function is responsible for setting it
on any successful read operation. See 10.10.2 CF-1152.14/CFStream.c.

By chance, at_eof seems to always have an initial value of false on
x86_64, but true on 32-bit x86. Crashpad’s Read() callback assumed that
the initial value was always false. The discrepancy caused truncation
and possibly hangs when a 32-bit process attempted to upload a request
body larger than 32kB, the buffer size used by NSMutableURLRequest or
something between it and CFReadStream.

A new test with more than 32kB of data is added.

As discussed in:
https://groups.google.com/a/chromium.org/d/topic/crashpad-dev/Vz--qMZJRPU

TEST=crashpad_util_test HTTPTransport.Upload33k
BUG=
R=rsesek@chromium.org

Review URL: https://codereview.chromium.org/1304433004 .
2015-08-18 15:42:34 -04:00

226 lines
6.7 KiB
Plaintext

// Copyright 2014 The Crashpad Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "util/net/http_transport.h"
#include <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#include "base/mac/foundation_util.h"
#import "base/mac/scoped_nsobject.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "third_party/apple_cf/CFStreamAbstract.h"
#include "util/net/http_body.h"
namespace crashpad {
namespace {
// An implementation of CFReadStream. This implements the V0 callback
// scheme.
class HTTPBodyStreamCFReadStream {
public:
explicit HTTPBodyStreamCFReadStream(HTTPBodyStream* body_stream)
: body_stream_(body_stream) {
}
// Creates a new NSInputStream, which the caller owns.
NSInputStream* CreateInputStream() {
CFStreamClientContext context = {
.version = 0,
.info = this,
.retain = nullptr,
.release = nullptr,
.copyDescription = nullptr
};
const CFReadStreamCallBacksV0 callbacks = {
.version = 0,
.open = &Open,
.openCompleted = &OpenCompleted,
.read = &Read,
.getBuffer = &GetBuffer,
.canRead = &CanRead,
.close = &Close,
.copyProperty = &CopyProperty,
.schedule = &Schedule,
.unschedule = &Unschedule
};
CFReadStreamRef read_stream = CFReadStreamCreate(nullptr,
reinterpret_cast<const CFReadStreamCallBacks*>(&callbacks), &context);
return base::mac::CFToNSCast(read_stream);
}
private:
static HTTPBodyStream* GetStream(void* info) {
return static_cast<HTTPBodyStreamCFReadStream*>(info)->body_stream_;
}
static Boolean Open(CFReadStreamRef stream,
CFStreamError* error,
Boolean* open_complete,
void* info) {
*open_complete = TRUE;
return TRUE;
}
static Boolean OpenCompleted(CFReadStreamRef stream,
CFStreamError* error,
void* info) {
return TRUE;
}
static CFIndex Read(CFReadStreamRef stream,
UInt8* buffer,
CFIndex buffer_length,
CFStreamError* error,
Boolean* at_eof,
void* info) {
if (buffer_length == 0) {
*at_eof = FALSE;
return 0;
}
ssize_t bytes_read = GetStream(info)->GetBytesBuffer(buffer, buffer_length);
if (bytes_read < 0) {
error->error = -1;
error->domain = kCFStreamErrorDomainCustom;
} else {
*at_eof = bytes_read == 0;
}
return bytes_read;
}
static const UInt8* GetBuffer(CFReadStreamRef stream,
CFIndex max_bytes_to_read,
CFIndex* num_bytes_read,
CFStreamError* error,
Boolean* at_eof,
void* info) {
return nullptr;
}
static Boolean CanRead(CFReadStreamRef stream, void* info) {
return TRUE;
}
static void Close(CFReadStreamRef stream, void* info) {}
static CFTypeRef CopyProperty(CFReadStreamRef stream,
CFStringRef property_name,
void* info) {
return nullptr;
}
static void Schedule(CFReadStreamRef stream,
CFRunLoopRef run_loop,
CFStringRef run_loop_mode,
void* info) {}
static void Unschedule(CFReadStreamRef stream,
CFRunLoopRef run_loop,
CFStringRef run_loop_mode,
void* info) {}
HTTPBodyStream* body_stream_; // weak
DISALLOW_COPY_AND_ASSIGN(HTTPBodyStreamCFReadStream);
};
class HTTPTransportMac final : public HTTPTransport {
public:
HTTPTransportMac();
~HTTPTransportMac() override;
bool ExecuteSynchronously(std::string* response_body) override;
private:
DISALLOW_COPY_AND_ASSIGN(HTTPTransportMac);
};
HTTPTransportMac::HTTPTransportMac() : HTTPTransport() {
}
HTTPTransportMac::~HTTPTransportMac() {
}
bool HTTPTransportMac::ExecuteSynchronously(std::string* response_body) {
DCHECK(body_stream());
@autoreleasepool {
NSString* url_ns_string = base::SysUTF8ToNSString(url());
NSURL* url = [NSURL URLWithString:url_ns_string];
NSMutableURLRequest* request =
[NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:timeout()];
[request setHTTPMethod:base::SysUTF8ToNSString(method())];
for (const auto& pair : headers()) {
[request setValue:base::SysUTF8ToNSString(pair.second)
forHTTPHeaderField:base::SysUTF8ToNSString(pair.first)];
}
HTTPBodyStreamCFReadStream body_stream_cf(body_stream());
base::scoped_nsobject<NSInputStream> input_stream(
body_stream_cf.CreateInputStream());
[request setHTTPBodyStream:input_stream.get()];
NSURLResponse* response = nil;
NSError* error = nil;
NSData* body = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response
error:&error];
if (error) {
LOG(ERROR) << [[error localizedDescription] UTF8String] << " ("
<< [[error domain] UTF8String] << " " << [error code] << ")";
return false;
}
if (!response) {
LOG(ERROR) << "no response";
return false;
}
NSHTTPURLResponse* http_response =
base::mac::ObjCCast<NSHTTPURLResponse>(response);
if (!http_response) {
LOG(ERROR) << "no http_response";
return false;
}
NSInteger http_status = [http_response statusCode];
if (http_status != 200) {
LOG(ERROR) << base::StringPrintf("HTTP status %ld",
implicit_cast<long>(http_status));
return false;
}
if (response_body) {
response_body->assign(static_cast<const char*>([body bytes]),
[body length]);
}
return true;
}
}
} // namespace
// static
scoped_ptr<HTTPTransport> HTTPTransport::Create() {
return scoped_ptr<HTTPTransport>(new HTTPTransportMac());
}
} // namespace crashpad