[libc] Add simple long double to printf float fuzz (#68449)

Recent testing has uncovered some hard-to-find bugs in printf's long
double support. This patch adds an extra long double path to the fuzzer
with minimal extra effort. While a more thorough long double fuzzer
would be useful, it would need to handle the non-standard cases of 80
bit long doubles such as unnormal and pseudo-denormal numbers. For that
reason, a standalone long double fuzzer is left for future development.
This commit is contained in:
michaelrj-google
2023-10-16 13:32:34 -07:00
committed by GitHub
parent 119b0f3895
commit 8a47ad4b67
3 changed files with 30 additions and 8 deletions

View File

@@ -29,6 +29,14 @@ inline bool simple_streq(char *first, char *second, int length) {
return true;
}
inline int simple_strlen(const char *str) {
int i = 0;
for (; *str; ++str, ++i) {
;
}
return i;
}
enum class TestResult {
Success,
BufferSizeFailed,
@@ -36,7 +44,8 @@ enum class TestResult {
StringsNotEqual,
};
inline TestResult test_vals(const char *fmt, double num, int prec, int width) {
template <typename F>
inline TestResult test_vals(const char *fmt, F num, int prec, int width) {
// Call snprintf on a nullptr to get the buffer size.
int buffer_size = LIBC_NAMESPACE::snprintf(nullptr, 0, fmt, width, prec, num);
@@ -70,10 +79,7 @@ inline TestResult test_vals(const char *fmt, double num, int prec, int width) {
}
constexpr char const *fmt_arr[] = {
"%*.*f",
"%*.*e",
"%*.*g",
"%*.*a",
"%*.*f", "%*.*e", "%*.*g", "%*.*a", "%*.*Lf", "%*.*Le", "%*.*Lg", "%*.*La",
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
@@ -100,6 +106,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
num = LIBC_NAMESPACE::fputil::FPBits<double>(raw_num).get_val();
// While we could create a "ld_raw_num" from additional bytes, it's much
// easier to stick with simply casting num to long double. This avoids the
// issues around 80 bit long doubles, especially unnormal and pseudo-denormal
// numbers, which MPFR doesn't handle well.
long double ld_num = static_cast<long double>(num);
if (width > MAX_SIZE) {
width = MAX_SIZE;
} else if (width < -MAX_SIZE) {
@@ -114,7 +126,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
for (size_t cur_fmt = 0; cur_fmt < sizeof(fmt_arr) / sizeof(char *);
++cur_fmt) {
TestResult result = test_vals(fmt_arr[cur_fmt], num, prec, width);
int fmt_len = simple_strlen(fmt_arr[cur_fmt]);
TestResult result;
if (fmt_arr[cur_fmt][fmt_len - 2] == 'L') {
result = test_vals<long double>(fmt_arr[cur_fmt], ld_num, prec, width);
} else {
result = test_vals<double>(fmt_arr[cur_fmt], num, prec, width);
}
if (result != TestResult::Success) {
__builtin_trap();
}