terminal/src/terminal/parser/ft_fuzzer/fuzzing_logic.h
2021-11-25 00:28:27 +01:00

494 lines
19 KiB
C++

//
// fuzzing_logic.h
//
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// The directed fuzzing logic header. Must be paired together with fuzzing_directed.h.
//
#ifndef __FUZZING_LOGIC_H__
#define __FUZZING_LOGIC_H__
#pragma once
#include "fuzzing_directed.h"
#include "string_helper.h"
#ifdef __GENERATE_DIRECTED_FUZZING
#include <strsafe.h>
namespace fuzz
{
//
// Fuzzing manipulations follow the following name conventions:
// _[fz/const]_[type]_[description]
// [fz/const] : separates randomized actions versus those that produce a constant effect
// [type] : the type of data that is being fuzzed, abbreviated by its hungarian notation
// [description] : describe the manipulation that is being applied
//
//
// The string fuzzing functions are designed to allow the caller to determine if NULL
// termination is necessary. For an example of the appropriate way to call any of the
// _fz_wsz_* functions, see FuzzStringW and FuzzStringW_NoRealloc.
//
// Inserts a format character to a random location within the string.
// Note that rcch is the count of characters (minus the NULL terminator), not the count of bytes.
static LPWSTR _fz_wsz_addFormatChar(__inout_ecount(rcch) WCHAR* pwsz, __inout size_t& rcch)
{
if (rcch > 1)
{
const PCWSTR rgFormatStringChars[] = { L"%n", L"%s", L"%d" };
size_t cbDestSize = 2 * sizeof(WCHAR);
memcpy_s(
&(pwsz[CFuzzChance::GetRandom<size_t>(rcch - 1)]), // -1 because we are writing 2 chars
cbDestSize,
CFuzzChance::SelectOne<const PCWSTR>(rgFormatStringChars, ARRAYSIZE(rgFormatStringChars)),
cbDestSize);
}
return pwsz;
}
// Inserts a format character to a random location within the string.
// Note that rcch does not include the NULL terminator
static LPSTR _fz_sz_addFormatChar(__inout_ecount(rcch) CHAR* psz, __inout size_t& rcch)
{
if (rcch > 1)
{
const LPCSTR rgFormatStringChars[] = { "%n", "%s", "%d" };
size_t cbDestSize = 2 * sizeof(CHAR);
memcpy_s(
&(psz[CFuzzChance::GetRandom<size_t>(rcch - 1)]),
cbDestSize,
CFuzzChance::SelectOne<const LPCSTR>(rgFormatStringChars, ARRAYSIZE(rgFormatStringChars)),
cbDestSize);
}
return psz;
}
// Adds a character related to paths to a random location within the string.
// Note that rcch is the count of characters (minus the NULL terminator), not the count of bytes.
static LPWSTR _fz_wsz_addPathChar(__inout_ecount(rcch) WCHAR* pwsz, __inout size_t& rcch)
{
if (rcch > 0)
{
const WCHAR rgPathChars[] = { L'.', L'\\', L'/', L':', L',', L';' };
pwsz[CFuzzChance::GetRandom<size_t>(rcch)] =
CFuzzChance::SelectOne<const WCHAR>(rgPathChars, ARRAYSIZE(rgPathChars));
}
return pwsz;
}
// Adds a character related to paths to a random location within the string.
// Note that rcch does not include the NULL terminator
static LPSTR _fz_sz_addPathChar(__inout_ecount(rcch) CHAR* psz, __inout size_t& rcch)
{
if (rcch > 0)
{
const CHAR rgPathChars[] = { '.', '\\', '/', ':', ',', ';' };
psz[CFuzzChance::GetRandom<size_t>(rcch)] =
CFuzzChance::SelectOne<const CHAR>(rgPathChars, ARRAYSIZE(rgPathChars));
}
return psz;
}
// Adds an invalid path character to a random location within the string.
// Note that rcch is the count of characters (minus the NULL terminator), not the count of bytes.
static LPWSTR _fz_wsz_addInvalidPathChar(__inout_ecount(rcch) WCHAR* pwsz, __inout size_t& rcch)
{
if (rcch > 0)
{
const WCHAR rgInvalidPathChars[] = { L'?', L'<', L'>', L'"', L'|', L'*' };
pwsz[CFuzzChance::GetRandom<size_t>(rcch)] =
CFuzzChance::SelectOne<const WCHAR>(rgInvalidPathChars, ARRAYSIZE(rgInvalidPathChars));
}
return pwsz;
}
// Adds an invalid path character to a random location within the string.
// Note that rcch does not include the NULL terminator
static LPSTR _fz_sz_addInvalidPathChar(__inout_ecount(rcch) CHAR* psz, __inout size_t& rcch)
{
if (rcch > 0)
{
const CHAR rgInvalidPathChars[] = { '?', '<', '>', '"', '|', '*' };
psz[CFuzzChance::GetRandom<size_t>(rcch)] =
CFuzzChance::SelectOne<const CHAR>(rgInvalidPathChars, ARRAYSIZE(rgInvalidPathChars));
}
return psz;
}
// Implementation depends on CFuzzLogic class and is therefore
// declared after CFuzzLogic is defined below.
template<typename _Type>
static _Type* _fz_flipBYTE(__inout_ecount(rcelms) _Type* p, __inout size_t& rcelms);
template<typename _Type>
static _Type* _fz_flipWCHAR(__inout_ecount(rcelms) _Type* p, __inout size_t& rcelms);
static char* _fz_sz_tokenizeSpaces(__in char* psz);
// Mirrors the first half of the string across the second half of the string.
// Note that rcch is the count of characters (minus the NULL terminator), not the count of bytes.
static LPWSTR _const_wsz_mirror(__inout_ecount(rcch) WCHAR* pwsz, __inout size_t& rcch)
{
if (rcch > 0)
{
size_t cchStart = 0;
size_t cchEnd = rcch - 1;
while (cchStart < cchEnd)
{
pwsz[cchEnd--] = pwsz[cchStart++];
}
}
return pwsz;
}
// Mirrors the first half of the string across the second half of the string.
// Note that rcch does not include the NULL terminator
static LPSTR _const_sz_mirror(__inout_ecount(rcch) CHAR* psz, __inout size_t& rcch)
{
if (rcch > 0)
{
size_t cchStart = 0;
size_t cchEnd = rcch - 1;
while (cchStart < cchEnd)
{
psz[cchEnd--] = psz[cchStart++];
}
}
return psz;
}
// Replicates the string repeatedly until the end of the buffer is reached.
// Note that rcch is the count of characters (minus the NULL terminator), not the count of bytes.
static LPWSTR _const_wsz_replicate(__inout_ecount(rcch) WCHAR* pwsz, __inout size_t& rcch)
{
if (rcch > 0)
{
size_t cch = wcslen(pwsz);
size_t cchStart = 0;
while (cch < rcch)
{
pwsz[cch++] = pwsz[cchStart++];
}
}
return pwsz;
}
// Replicates the string repeatedly until the end of the buffer is reached.
// Note that rcch does not include the NULL terminator
static LPSTR _const_sz_replicate(__inout_ecount(rcch) CHAR* psz, __inout size_t& rcch)
{
if (rcch > 0)
{
size_t cch = strlen(psz);
size_t cchStart = 0;
while (cch < rcch)
{
psz[cch++] = psz[cchStart++];
}
}
return psz;
}
// Replaces the string with a valid system path to shell32.dll in the system32 dir.
// Note that rcch is the count of characters (minus the NULL terminator), not the count of bytes.
static LPWSTR _const_wsz_validPath(__inout_ecount(rcch) WCHAR* pwsz, __inout size_t& rcch)
{
WCHAR wszSystemDirectory[MAX_PATH] = { 0 };
if (GetSystemDirectoryW(wszSystemDirectory, ARRAYSIZE(wszSystemDirectory)))
{
StringCchPrintfW(pwsz, rcch, L"%s\\shell32.dll", wszSystemDirectory);
}
return pwsz;
}
// Replaces the string with a valid system path to shell32.dll in the system32 dir.
// Note that rcch does not include the NULL terminator
static LPSTR _const_sz_validPath(__inout_ecount(rcch) CHAR* psz, __inout size_t& rcch)
{
CHAR szSystemDirectory[MAX_PATH] = { 0 };
if (GetSystemDirectoryA(szSystemDirectory, ARRAYSIZE(szSystemDirectory)))
{
StringCchPrintfA(psz, rcch, "%s\\shell32.dll", szSystemDirectory);
}
return psz;
}
// Reverses the string in place.
static LPWSTR _const_wsz_reverse(__inout WCHAR* pwsz, __inout size_t&)
{
return _wcsrev(pwsz);
}
// Reverses the string in place.
static LPSTR _const_sz_reverse(__inout CHAR* psz, __inout size_t&)
{
return _strrev(psz);
}
template<class _Alloc = CFuzzCRTAllocator>
class CFuzzLogic;
// Flips a random byte value within the buffer.
template<typename _Type>
static _Type* _fz_flipByte(__inout_ecount(rcelms) _Type* p, __inout size_t& rcelms)
{
if (rcelms > 0)
{
return reinterpret_cast<_Type*>(CFuzzLogic<>::FuzzArrayElement(
reinterpret_cast<BYTE*>(p), (rcelms) * sizeof(_Type)));
}
return p;
}
// Flips a random entry value within the buffer
template<typename _Type>
static _Type* _fz_flipEntry(__inout_ecount(rcelms) _Type* p, __inout size_t& rcelms)
{
if (rcelms > 0)
{
return CFuzzLogic<>::FuzzArrayElement(p, rcelms);
}
return p;
}
// Contains fuzzing logic based upon a variety of default scenarios. The
// idea is to capture and make available a comprehensive fuzzing library
// that does not require external modules or complex setup. This should
// make fuzzing easier to implement and test, as well as more explicit
// with regard to what fuzzing manipulations are possible.
template<class _Alloc>
class CFuzzLogic
{
public:
// Permutes a random element of the array with a valid value that can be
// contained within the size of a single element. See _fz_wsz_flipBYTE
// and _fz_wsz_flipWCHAR for an example of how the element size determines
// the amount of data manipulated.
template<typename _Type>
static _Type* FuzzArrayElement(__in_ecount(cElems) _Type* rg, __in size_t cElems) throw()
{
if (rg && cElems)
{
rg[CFuzzChance::GetRandom<size_t>(cElems)] =
static_cast<_Type>(CFuzzChance::GetRandom<size_t>(
static_cast<size_t>(pow(static_cast<double>(2), static_cast<double>(sizeof(_Type) * 8)))));
}
return rg;
}
// Fuzzes a string by allocating a new fuzzed string. Note that the string
// length can shrink or grow in relation to the template data passed in
// via pwsz. The maximum size the string can grow is 2 times the current
// length.
static LPWSTR FuzzStringW(__in LPCWSTR pwsz) throw()
{
const _fuzz_type_entry<size_t> rgfte[] = {
{ 10, [](size_t cch) { return CFuzzChance::GetRandom<size_t>(cch + 1); } },
{ 50, [](size_t cch) { return cch + CFuzzChance::GetRandom<size_t>(cch + 1); } }
};
CFuzzType<size_t> fuzz_cb(FUZZ_MAP(rgfte), wcslen(pwsz));
size_t cch = fuzz_cb + 1; // add 1 for ensuring NULL termination
LPWSTR pwszRealloc = reinterpret_cast<LPWSTR>(_Alloc::Allocate(cch * sizeof(WCHAR)));
if (pwszRealloc)
{
pwszRealloc[--cch] = L'\0';
StringCchCopyW(pwszRealloc, cch, pwsz);
FuzzStringW_NoRealloc(pwszRealloc, cch);
}
return pwszRealloc;
}
// Fuzzes a string by allocating a new fuzzed string. Note that the string
// length can shrink or grow in relation to the template data passed in
// via pwsz. The maximum size the string can grow is 2 times the current
// length.
static LPSTR FuzzStringA(__in LPCSTR psz) throw()
{
LPSTR pszRealloc = nullptr;
const _fuzz_type_entry<size_t> rgfte[] = {
{ 10, [](size_t cch) { return CFuzzChance::GetRandom<size_t>(cch + 1); } },
{ 50, [](size_t cch) { return cch + CFuzzChance::GetRandom<size_t>(cch + 1); } }
};
CFuzzType<size_t> fuzz_cch(FUZZ_MAP(rgfte), strlen(psz));
size_t cchTemp = fuzz_cch + 1; // add 1 for ensuring NULL termination
LPSTR pszReallocTemp = reinterpret_cast<LPSTR>(_Alloc::Allocate(cchTemp * sizeof(CHAR)));
if (pszReallocTemp)
{
pszReallocTemp[--cchTemp] = '\0';
StringCchCopyA(pszReallocTemp, cchTemp, psz);
const _fuzz_type_entry<LPSTR> fuzzMap[] = {
{ 5, _fz_sz_tokenizeSpaces, FreeFuzzedBuffer },
{ 95, [=](LPSTR p) {
size_t cchInner = cchTemp;
return FuzzStringA_NoRealloc(p, cchInner);
} }
};
CFuzzString<__FUZZING_ALLOCATOR, CHAR> eval(FUZZ_MAP(fuzzMap), pszReallocTemp);
// Performance optimization: 95% of the time the buffer returned from eval will not be reallocated
// and thus will still be pointing at pszReallocTemp. In this case, it is not necessary to duplicate
// the string a second time, just transfer ownership to the calling function.
if ((LPSTR)eval == pszReallocTemp)
{
pszRealloc = pszReallocTemp;
}
else
{
pszRealloc = DuplicateStringA(eval);
FreeFuzzedBuffer(pszReallocTemp);
}
}
return pszRealloc;
}
// Fuzzes a string in place, no new memory is allocated to perform this
// fuzzing. This means that the return value is the same as the pwsz
// parameter.
static LPWSTR FuzzStringW_NoRealloc(__inout LPWSTR pwsz) throw()
{
size_t cch = wcslen(pwsz);
return FuzzStringW_NoRealloc(pwsz, cch);
}
// Fuzzes a string in place, no new memory is allocated to perform this
// fuzzing. This means that the return value is the same as the psz
// parameter.
static LPSTR FuzzStringA_NoRealloc(__inout LPSTR psz) throw()
{
size_t cch = strlen(psz);
return FuzzStringA_NoRealloc(psz, cch);
}
static LPSTR DuplicateStringA(__in LPCSTR psz) throw()
{
size_t cch = strlen(psz) + 1;
LPSTR pszDuplicate = reinterpret_cast<LPSTR>(_Alloc::Allocate(cch * sizeof(CHAR)));
if (pszDuplicate)
{
StringCchCopyA(pszDuplicate, cch, psz);
}
return pszDuplicate;
}
// This function will free any allocations that are made as part of this
// class. Specifically, fuzzed strings created with FuzzStringW should
// always be freed with FreeFuzzedBuffer. The prototype of this function
// should always support being used within a fuzz array entry or a fuzz
// type entry as the pfnDealloc function.
static void FreeFuzzedBuffer(void* pv) throw()
{
_Alloc::Free(pv);
}
private:
CFuzzLogic(){};
virtual ~CFuzzLogic(){};
static LPWSTR FuzzStringW_NoRealloc(__inout LPWSTR pwsz, __inout size_t& rcch)
{
if (rcch > 0)
{
const _fuzz_array_entry<WCHAR, size_t> rgfae[] = {
// small randomized manipulations
{ 21, _fz_wsz_addFormatChar },
{ 21, _fz_wsz_addPathChar },
{ 21, _fz_wsz_addInvalidPathChar },
{ 11, [](WCHAR* pwsz, size_t& rcch) { return _fz_flipByte(pwsz, rcch); } },
{ 10, [](WCHAR* pwsz, size_t& rcch) { return _fz_flipEntry(pwsz, rcch); } },
// non-random manipulations
{ 4, _const_wsz_replicate },
{ 4, _const_wsz_mirror },
{ 4, _const_wsz_validPath },
{ 4, _const_wsz_reverse }
};
CFuzzArray<__FUZZING_ALLOCATOR, WCHAR, size_t> fa(FUZZ_MAP(rgfae), pwsz, rcch);
fa.GetValueFromMap();
}
return pwsz;
}
static LPSTR FuzzStringA_NoRealloc(__inout LPSTR psz, __inout size_t& rcch)
{
if (rcch > 0)
{
const _fuzz_array_entry<CHAR, size_t> rgfae[] = {
// small randomized manipulations
{ 21, _fz_sz_addFormatChar },
{ 21, _fz_sz_addPathChar },
{ 21, _fz_sz_addInvalidPathChar },
{ 21, [](CHAR* psz, size_t& rcch) { return _fz_flipByte<CHAR>(psz, rcch); } },
// non-random manipulations
{ 4, _const_sz_replicate },
{ 4, _const_sz_mirror },
{ 4, _const_sz_validPath },
{ 4, _const_sz_reverse }
};
CFuzzArray<__FUZZING_ALLOCATOR, CHAR, size_t> fa(FUZZ_MAP(rgfae), psz, rcch);
fa.GetValueFromMap();
}
return psz;
}
};
static char* _fz_sz_tokenizeSpaces(__in char* psz)
{
const _fuzz_type_entry<DWORD> repeatMap[] = {
{ 10, [](DWORD) { return 0; } },
{ 10, [](DWORD) { return 2; } },
{ 1, [](DWORD) { return CFuzzChance::GetRandom<DWORD>(0xF); } }
};
std::string sFuzzed;
char* next_token = nullptr;
char* token = strtok_s(psz, " ", &next_token);
while (token)
{
CFuzzType<DWORD> repeat(FUZZ_MAP(repeatMap), 1);
for (DWORD i = 0; i < (DWORD)repeat; i++)
{
sFuzzed += token;
sFuzzed += " ";
}
token = strtok_s(nullptr, " ", &next_token);
}
// If psz has a final trailing space, avoid trimming it away. Otherwise, remove
// the extra added final space appended via the loop above.
size_t cch = strlen(psz);
if (psz[cch] == ' ')
TrimRight(sFuzzed, ' ');
return CFuzzLogic<>::DuplicateStringA(sFuzzed.c_str());
}
}
#endif
#endif