/* * Author: Landon Fuller * * Copyright (c) 2008-2009 Plausible Labs Cooperative, Inc. * All rights reserved. * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #import "SenTestCompat.h" #import "PLCrashAsync.h" #import #import @interface PLCrashAsyncTests : SenTestCase { @private /* Path to test output file */ __strong NSString *_outputFile; /* Open output file descriptor */ int _testFd; } @end @implementation PLCrashAsyncTests - (void) setUp { /* Create a temporary output file */ _outputFile = [NSTemporaryDirectory() stringByAppendingString: [[NSProcessInfo processInfo] globallyUniqueString]]; _testFd = open([_outputFile UTF8String], O_RDWR|O_CREAT|O_EXCL, 0644); STAssertTrue(_testFd >= 0, @"Could not open test output file"); } - (void) tearDown { NSError *error; /* Close the file (it may already be closed) */ close(_testFd); /* Delete the file */ STAssertTrue([[NSFileManager defaultManager] removeItemAtPath: _outputFile error: &error], @"Could not remove log file"); _outputFile = nil; } - (void) testByteOrderDetection { if (OSHostByteOrder() == OSLittleEndian) { STAssertEquals(plcrash_async_byteorder_little_endian(), &plcrash_async_byteorder_direct, @"Incorrect byte order"); STAssertEquals(plcrash_async_byteorder_big_endian(), &plcrash_async_byteorder_swapped, @"Incorrect byte order"); } else if (OSHostByteOrder() == OSBigEndian) { STAssertEquals(plcrash_async_byteorder_big_endian(), &plcrash_async_byteorder_direct, @"Incorrect byte order"); STAssertEquals(plcrash_async_byteorder_little_endian(), &plcrash_async_byteorder_swapped, @"Incorrect byte order"); } else { STFail(@"Unknown byte order"); } } - (void) testApplyAddress { pl_vm_address_t result; /* Verify standard operation */ STAssertTrue(plcrash_async_address_apply_offset(1, 1, &result), @"Failed to apply offset"); STAssertEquals((pl_vm_address_t)2, result, @"Incorrect address returned"); /* Verify negative offset handling */ STAssertTrue(plcrash_async_address_apply_offset(1, -1, &result), @"Failed to apply offset"); STAssertEquals((pl_vm_address_t)0, result, @"Incorrect address returned"); /* Verify that overflow is safely handled */ STAssertFalse(plcrash_async_address_apply_offset(PL_VM_ADDRESS_MAX, 1, &result), @"Bad adddress was accepted"); STAssertFalse(plcrash_async_address_apply_offset(1, -2, &result), @"Bad adddress was accepted"); } - (void) testTaskMemcpyAddr { const char bytes[] = "Hello"; char dest[sizeof(bytes)]; // Verify that a good read succeeds plcrash_async_task_memcpy(mach_task_self(), (pl_vm_address_t) bytes, 1, dest, sizeof(dest)); STAssertTrue(strcmp(bytes+1, dest) == 0, @"Read was not performed"); // Verify that reading off the page at 0x0 fails STAssertNotEquals(PLCRASH_ESUCCESS, plcrash_async_task_memcpy(mach_task_self(), 0, 0, dest, sizeof(bytes)), @"Bad read was performed"); // Verify that overflow is safely handled STAssertEquals(PLCRASH_ENOMEM, plcrash_async_task_memcpy(mach_task_self(), PL_VM_ADDRESS_MAX, 1, dest, sizeof(bytes)), @"Bad read was performed"); } - (void) testTaskReadInt { const plcrash_async_byteorder_t *byteorder = &plcrash_async_byteorder_swapped; union test_data { uint8_t u8; uint16_t u16; uint32_t u32; uint64_t u64; }; union test_data src; union test_data dest; src.u64 = 0xCAFEF00DDEADBEEFULL; STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_task_read_uint64(mach_task_self(), byteorder, (pl_vm_address_t) &src, 0, &dest.u64), @"Failed to read value"); STAssertEquals(byteorder->swap64(dest.u64), src.u64, @"Incorrect value read"); STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_task_read_uint32(mach_task_self(), byteorder, (pl_vm_address_t) &src, 0, &dest.u32), @"Failed to read value"); STAssertEquals(byteorder->swap32(dest.u32), src.u32, @"Incorrect value read"); STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_task_read_uint16(mach_task_self(), byteorder, (pl_vm_address_t) &src, 0, &dest.u16), @"Failed to read value"); STAssertEquals(byteorder->swap16(dest.u16), src.u16, @"Incorrect value read"); STAssertEquals(PLCRASH_ESUCCESS, plcrash_async_task_read_uint8(mach_task_self(), (pl_vm_address_t) &src, 0, &dest.u8), @"Failed to read value"); STAssertEquals(dest.u8, src.u8, @"Incorrect value read"); } - (void) testStrcmp { STAssertEquals(0, plcrash_async_strcmp("s1", "s1"), @"Strings should be equal"); STAssertTrue(plcrash_async_strcmp("s1", "s2") < 0, @"Strings compared incorrectly"); STAssertTrue(plcrash_async_strcmp("s2", "s1") > 0, @"Strings compared incorrectly"); /* If these don't crash, success. Of course, it these probably won't crash even if they do over-read; we should * probably modify these to sit on the edge of a page boundary. */ STAssertTrue(plcrash_async_strcmp("longer", "s") != 0, @""); STAssertTrue(plcrash_async_strcmp("s", "longer") != 0, @""); } - (void) testStrncmp { STAssertEquals(0, plcrash_async_strncmp("s1", "s1", 42), @"Strings should be equal"); STAssertTrue(plcrash_async_strncmp("s1", "s2", 42) < 0, @"Strings compared incorrectly"); STAssertTrue(plcrash_async_strncmp("s2", "s1", 42) > 0, @"Strings compared incorrectly"); /* If these don't crash, success. Of course, it these probably won't crash even if they do over-read */ STAssertTrue(plcrash_async_strncmp("longer", "s", 9999999) != 0, @""); STAssertTrue(plcrash_async_strncmp("s", "longer", 9999999) != 0, @""); /* Make sure the "n" works */ STAssertEquals(plcrash_async_strncmp("aaaaaaaaaa", "abbbbbbbbb", 1), 0, @"String prefixes should be equal"); STAssertTrue(plcrash_async_strncmp("aaaaaaaaaa", "abbbbbbbbb", 9) < 0, @"String prefixes should be equal"); STAssertEquals(plcrash_async_strncmp("aaaaaaaaaa", "aaaaaaaaab", 9), 0, @"String prefixes should be equal"); } - (void) testMemcpy { size_t size = 1024; uint8_t template[size]; uint8_t src[size+1]; uint8_t dest[size+1]; /* Create our template. We don't use the template as the source, as it's possible the memcpy implementation * could modify src in error, while validation could still succeed if src == dest. */ memset_pattern4(template, (const uint8_t[]){ 0xC, 0xA, 0xF, 0xE }, size); memcpy(src, template, size); /* Add mismatched sentinals to the destination and src; serves as a simple check for overrun on write. */ src[1024] = 0xD; dest[1024] = 0xB; plcrash_async_memcpy(dest, src, size); STAssertTrue(memcmp(template, dest, size) == 0, @"The copied destination does not match the source"); STAssertTrue(dest[1024] == (uint8_t)0xB, @"Sentinal was overwritten (0x%" PRIX8 ")", dest[1024]); } - (void) testMemset { size_t size = 1024; uint8_t template[size]; uint8_t dest[size+1]; /* Create our template. */ memset(template, 0xAB, size); /* Add mismatched sentinals to the destination; serves as a simple check for overrun on write. */ dest[1024] = 0xB; plcrash_async_memset(dest, template[0], size); STAssertTrue(memcmp(template, dest, size) == 0, @"The copied destination does not match the source"); STAssertTrue(dest[1024] == (uint8_t)0xB, @"Sentinal was overwritten (0x%" PRIX8 ")", dest[1024]); } - (void) testWriteLimits { plcrash_async_file_t file; uint32_t data = 1; /* Initialize the file instance with an 8 byte limit */ plcrash_async_file_init(&file, _testFd, 8); /* Write to the limit */ STAssertTrue(plcrash_async_file_write(&file, &data, sizeof(data)), @"Write failed"); STAssertTrue(plcrash_async_file_write(&file, &data, sizeof(data)), @"Write failed"); STAssertFalse(plcrash_async_file_write(&file, &data, sizeof(data)), @"Limit not enforced"); /* Close */ plcrash_async_file_flush(&file); plcrash_async_file_close(&file); /* Test the actual file size */ struct stat fs; stat([_outputFile UTF8String], &fs); STAssertEquals((off_t)8, fs.st_size, @"File size is not 8 bytes"); } /* * Read in the test file, verify that it matches the given data block. Returns the * total number of bytes read (which may be less than the data block, which will * not trigger an error). */ - (size_t) checkTestData: (void *) data bytes: (size_t) len inputStream: (NSInputStream *) input { size_t bytesRead = 0; while ([input hasBytesAvailable] && bytesRead < len) { unsigned char buf[256]; size_t nread; nread = [input read: buf maxLength: len-bytesRead]; STAssertTrue(memcmp(buf, data + bytesRead, nread) == 0, @"Data does not compare at %zu (just read %zu)", bytesRead, nread); bytesRead += nread; } return bytesRead; } - (void) testBufferedWrite { plcrash_async_file_t file; int write_iterations = 8; unsigned char data[100]; size_t nread = 0; STAssertTrue(sizeof(data) * write_iterations > sizeof(file.buffer), @"Test is invalid if our buffer is not larger"); /* Initialize the file instance */ plcrash_async_file_init(&file, _testFd, 0); /* Create test data */ for (unsigned char i = 0; i < sizeof(data); i++) data[i] = i; /* Write out the test data */ for (int i = 0; i < write_iterations; i++) STAssertTrue(plcrash_async_file_write(&file, data, sizeof(data)), @"Failed to write to output buffer"); /* Flush pending data and close the file */ STAssertTrue(plcrash_async_file_flush(&file), @"File flush failed"); STAssertTrue(plcrash_async_file_close(&file), @"File not closed"); /* Validate the test file */ NSInputStream *input = [NSInputStream inputStreamWithFileAtPath: _outputFile]; [input open]; STAssertEquals((NSStreamStatus)NSStreamStatusOpen, [input streamStatus], @"Could not open input stream %@: %@", _outputFile, [input streamError]); for (int i = 0; i < write_iterations; i++) nread += [self checkTestData: data bytes: sizeof(data) inputStream: input]; STAssertEquals(nread, sizeof(data) * write_iterations, @"Fewer than expected bytes were written (%zu < %zu)", nread, sizeof(data) * write_iterations); [input close]; } @end