Files
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

226 lines
11 KiB
Objective-C

/*
* Author: Landon Fuller <landonf@plausiblelabs.com>
*
* Copyright (c) 2008-2013 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 "PLCrashSysctl.h"
#include <sys/mman.h>
#include <mach/mach.h>
@interface PLCrashSysctlTests : SenTestCase @end
@implementation PLCrashSysctlTests
/* Test fetch of a string value */
- (void) testSysctlString {
char *string = plcrash_sysctl_string("kern.ostype");
STAssertNotNULL(string, @"Failed to fetch string value");
// This is a bit fragile
STAssertEqualCStrings(string, "Darwin", @"Did not fetch expected OS type");
free(string);
}
/* Test fetch of an integer value */
- (void) testSysctlInteger {
int result;
STAssertTrue(plcrash_sysctl_int("hw.logicalcpu_max", &result), @"Failed to fetch int value");
STAssertEquals(result, (int)[[NSProcessInfo processInfo] processorCount], @"Incorrect count");
}
/**
* @internal
*
* Internal implementation of the MAX_LEN_UTF8() and LEN_UTF8() macros used in PLCrashSysctlTests::testValidUTF8Strlen.
*
* Handles writing of the test data at the end of the first page within @a test_pages, where
* an overrun will trigger an access violation, and then returning the result of calling
* plcrash_sysctl_valid_utf8_bytes() or plcrash_sysctl_valid_utf8_bytes_max() on the written data.
*
* We could also implement this using GCC's non-standard statement exprs, rather than using an additional function.
*
* @param test_pages Page-aligned allocation of size PAGE_SIZE*2. The second page should be set PROT_NONE to ensure
* that over-read triggers an access violation.
* @param test_data The test data to write at the end of the first page within @a test_pages.
* @param test_data_len The length of @a test_data in bytes.
* @param maxvariant If true, plcrash_sysctl_valid_utf8_bytes_max() will be called. If false, plcrash_sysctl_valid_utf8_bytes().
* @param maxlen The maxlen value to be passed to plcrash_sysctl_valid_utf8_bytes_max().
*/
static size_t utf8_test_data_strlen_max (uint8_t *test_pages, uint8_t *test_data, size_t test_data_len, BOOL maxvariant, size_t maxlen) {
assert(test_data_len < PAGE_SIZE);
uint8_t *target = test_pages + (PAGE_SIZE - test_data_len);
memcpy(target, test_data, test_data_len);
if (maxvariant) {
return plcrash_sysctl_valid_utf8_bytes_max(target, maxlen);
} else {
return plcrash_sysctl_valid_utf8_bytes(target);
}
}
/* Test handling of truncated UTF-8 strings */
- (void) testValidUTF8Strlen {
/*
* For our tests, we set up a custom two-page allocation to detect read overruns.
*
* The first page has R|W permissions, while the second page is set to PROT_NONE; by
* positioning our test data on the trailing edge of the first page, we let the second
* page trigger an access violation.
*
* The allocation is cleaned up at the bottom of this method.
*/
uint8_t *test_pages;
{
/* Allocate the two pages */
test_pages = mmap(NULL, PAGE_SIZE*2, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, -1, 0);
STAssertNotEquals((void *)test_pages, MAP_FAILED, @"Failed to mmap() pages: %s", strerror(errno));
/* Mark the second page as non-writable, non-readable */
STAssertEquals(0, mprotect(test_pages+PAGE_SIZE, PAGE_SIZE, PROT_NONE), @"Failed to set page protections: %s", strerror(errno));
}
/* Position the given bytes within our test_pages buffer, returning the result of plcrash_sysctl_valid_utf8_bytes_max(). */
#define MAX_LEN_UTF8(maxlen, ...) \
utf8_test_data_strlen_max(test_pages, (uint8_t[]){ __VA_ARGS__ }, sizeof((uint8_t[]){ __VA_ARGS__ }), true, maxlen)
/* Test handling (and interaction) of maxlen and NUL termination */
{
STAssertEquals(MAX_LEN_UTF8(100, 'a', '\0'), (size_t)1, @"String iteration ignored NUL terminator in favor of maxlen");
STAssertEquals(MAX_LEN_UTF8(1, 'a', 'a', '\0'), (size_t)1, @"String iteration ignored maxlen in favor of NUL");
}
/* Test handling (and interaction) of maxlen and multibyte validation */
{
/* This is a valid multibyte sequences; we use maxlen to terminate in the middle of it */
STAssertEquals(MAX_LEN_UTF8(2, 'a', 0xC2, 0x80, '\0'), (size_t)1, @"Multibyte validation ignored maxlen");
/* Verify that maxlen doesn't trigger *early* termination. This also sanity-checks the above test,
* asserting that had maxlen not been set too low, the characters would have been correctly validated */
STAssertEquals(MAX_LEN_UTF8(3, 'a', 0xC2, 0x80, '\0'), (size_t)3, @"Maxlen value triggered incorrect early termination of multibyte validation");
}
#define LEN_UTF8(...) \
utf8_test_data_strlen_max(test_pages, (uint8_t[]){ __VA_ARGS__ }, sizeof((uint8_t[]){ __VA_ARGS__ }), false, 0)
/* Test BOM handling. BOM is not useful or recommended for UTF-8 encoding, but it's still necessary to support. */
{
STAssertEquals(LEN_UTF8(0xEF, 0xBB, 0xBF, 'a', '\0'), (size_t)4, @"0 continutation rejected in-range byte");
}
/* Test handling of a 0-length string. */
STAssertEquals(LEN_UTF8('\0'), (size_t)0, @"0 length NULL terminated string should return 0");
/* Test handling of a 0-length maxlen on a non-zero length string. */
STAssertEquals(MAX_LEN_UTF8(0, 'a', '\0'), (size_t)0,
@"String iteration ignored maxlen of a zero length string");
/* Test handling of strings that start with invalid UTF-8 byte(s) */
{
/* Invalid single byte character */
STAssertEquals(LEN_UTF8(128, '\0'), (size_t)0, @"Length should be zero on a string starting with invalid UTF-8");
/* Missing continuation */
STAssertEquals(LEN_UTF8(0xC0, '\0'), (size_t)0, @"Length should be zero on a string starting with invalid UTF-8");
}
/* Test 0 continuation sequence */
{
STAssertEquals(LEN_UTF8('a', 127, '\0'), (size_t)2, @"Rejected in-range byte");
STAssertEquals(LEN_UTF8('a', 128, '\0'), (size_t)1, @"Accepted out-of-range byte");
}
/* Test 1 byte continuation of 2 byte sequence */
{
/* Verify that bytes that fall within the expected range are accepted */
STAssertEquals(LEN_UTF8('a', 0xC2, 0x80, '\0'), (size_t)3, @"Rejected in-range byte (128)");
STAssertEquals(LEN_UTF8('a', 0xDF, 0xBF, '\0'), (size_t)3, @"Rejected in-range byte (2047)");
/* Verify that a missing byte in a 2 byte sequence is considered an error */
STAssertEquals(LEN_UTF8('a', 0xC0, '\0'), (size_t)1, @"Accepted leading byte with missing continuation");
/* Verify that an invalid 2nd byte in a 2 byte sequence is considered an error */
STAssertEquals(LEN_UTF8('a', 0xC0, 0x00, '\0'), (size_t)1, @"Accepted leading byte with invalid continuation");
}
/* Test 2 byte continuation of 3 byte sequence */
{
/* Verify that bytes that fall within the expected range are accepted */
STAssertEquals(LEN_UTF8('a', 0xE0, 0xA0, 0x80, '\0'), (size_t)4, @"Rejected in-range byte (2048)");
STAssertEquals(LEN_UTF8('a', 0xEF, 0xBF, 0xBF, '\0'), (size_t)4, @"Rejected in-range byte (65535)");
/* Verify that missing trailing bytes in a 3 byte sequence are considered an error */
STAssertEquals(LEN_UTF8('a', 0xE0, '\0'), (size_t)1, @"Accepted leading byte with missing continuations");
STAssertEquals(LEN_UTF8('a', 0xE0, 0x80, '\0'), (size_t)1, @"Accepted leading byte with missing continuations");
STAssertEquals(LEN_UTF8('a', 0xE0, 0x80, 0x80, '\0'), (size_t)4, @"Rejected sequence containing full byte allotment");
/* Verify that invalid trailing bytes in a 3 byte sequence are considered an error */
STAssertEquals(LEN_UTF8('a', 0xE0, 0x00, '\0'), (size_t)1, @"Accepted leading byte with invalid continuation");
STAssertEquals(LEN_UTF8('a', 0xE0, 0x80, 0x0, '\0'), (size_t)1, @"Accepted leading bytes with invalid continuation");
}
/* Test 3 byte continuation of 4 byte sequence */
{
/* Verify that bytes that fall within the expected range are accepted */
STAssertEquals(LEN_UTF8('a', 0xF0, 0x90, 0x80, 0x80, '\0'), (size_t)5, @"Rejected in-range byte (65536)");
STAssertEquals(LEN_UTF8('a', 0xF4, 0x8F, 0xBF, 0xBF, '\0'), (size_t)5, @"Rejected in-range byte (1114111)");
/* Verify that missing trailing bytes in a 4 byte sequence are considered an error */
STAssertEquals(LEN_UTF8('a', 0xF0, '\0'), (size_t)1, @"Accepted leading byte with missing continuations");
STAssertEquals(LEN_UTF8('a', 0xF0, 0x80, '\0'), (size_t)1, @"Accepted leading byte with missing continuations");
STAssertEquals(LEN_UTF8('a', 0xF0, 0x80, 0x80, 0x80, '\0'), (size_t)5, @"Rejected sequence containing full byte allotment");
/* Verify that invalid trailing bytes in a 4 byte sequence are considered an error */
STAssertEquals(LEN_UTF8('a', 0xF0, 0x00, '\0'), (size_t)1, @"Accepted leading byte with invalid continuation");
STAssertEquals(LEN_UTF8('a', 0xF0, 0x80, 0x0, '\0'), (size_t)1, @"Accepted leading bytes with invalid continuation");
STAssertEquals(LEN_UTF8('a', 0xF0, 0x80, 0x80, 0x0, '\0'), (size_t)1, @"Accepted leading bytes with invalid continuation");
}
/* Verify that the implementation correctly resets its internal state after fully parsing a UTF-8 code
* point; this is just a test of its behavior with a multiple codepoint string */
{
STAssertEquals(LEN_UTF8('a', 0xC2, 0x80, 0xC2, 0x80, '\0'), (size_t)5, @"Rejected valid UTF-8 string");
/* This should return the length of the valid bytes, ignoring the invalid trailing multibyte sequence */
STAssertEquals(LEN_UTF8('a', 0xC2, 0x80, /* Invalid */ 0xC2, '\0'), (size_t)3, @"Lost valid UTF-8 prefix when rejecting trailing bytes");
}
#undef LEN_UTF8
#undef MAX_LEN_UTF8
/* Clean up */
munmap(test_pages, PAGE_SIZE*2);
}
@end