[lldb/DataFormatter] Check for overflow when finding NSDate epoch

Summary:
Fixes UBSan-reported issues where the date value inside of an
uninitialized NSDate overflows the 64-bit epoch.

rdar://61774575

Reviewers: JDevlieghere, mib, teemperor

Subscribers: mgorny, lldb-commits

Tags: #lldb

Differential Revision: https://reviews.llvm.org/D80150
This commit is contained in:
Vedant Kumar 2020-04-21 10:00:13 -07:00
parent 47a0e9f49b
commit b783f70a42
4 changed files with 108 additions and 28 deletions

View File

@ -0,0 +1,26 @@
//===-- Mock.h --------------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_DATAFORMATTERS_MOCK_H
#define LLDB_DATAFORMATTERS_MOCK_H
namespace lldb_private {
class Stream;
namespace formatters {
namespace NSDate {
/// Format the date_value field of a NSDate.
bool FormatDateValue(double date_value, Stream &stream);
} // namespace NSDate
} // namespace formatters
} // namespace lldb_private
#endif // LLDB_DATAFORMATTERS_MOCK_H

View File

@ -13,6 +13,7 @@
#include "lldb/Core/ValueObject.h"
#include "lldb/Core/ValueObjectConstResult.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/DataFormatters/Mock.h"
#include "lldb/DataFormatters/StringPrinter.h"
#include "lldb/DataFormatters/TypeSummary.h"
#include "lldb/Host/Time.h"
@ -27,6 +28,7 @@
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/bit.h"
#include "llvm/Support/CheckedArithmetic.h"
#include "Plugins/LanguageRuntime/ObjC/AppleObjCRuntime/AppleObjCRuntime.h"
@ -785,6 +787,34 @@ static double decodeTaggedTimeInterval(uint64_t encodedTimeInterval) {
return llvm::bit_cast<double>(decodedBits);
}
bool lldb_private::formatters::NSDate::FormatDateValue(double date_value,
Stream &stream) {
if (date_value == -63114076800) {
stream.Printf("0001-12-30 00:00:00 +0000");
return true;
}
if (date_value > std::numeric_limits<time_t>::max() ||
date_value < std::numeric_limits<time_t>::min())
return false;
time_t epoch = GetOSXEpoch();
if (auto osx_epoch = llvm::checkedAdd(epoch, (time_t)date_value))
epoch = *osx_epoch;
else
return false;
tm *tm_date = gmtime(&epoch);
if (!tm_date)
return false;
std::string buffer(1024, 0);
if (strftime(&buffer[0], 1023, "%Z", tm_date) == 0)
return false;
stream.Printf("%04d-%02d-%02d %02d:%02d:%02d %s", tm_date->tm_year + 1900,
tm_date->tm_mon + 1, tm_date->tm_mday, tm_date->tm_hour,
tm_date->tm_min, tm_date->tm_sec, buffer.c_str());
return true;
}
bool lldb_private::formatters::NSDateSummaryProvider(
ValueObject &valobj, Stream &stream, const TypeSummaryOptions &options) {
ProcessSP process_sp = valobj.GetProcessSP();
@ -828,6 +858,16 @@ bool lldb_private::formatters::NSDateSummaryProvider(
if (descriptor->GetTaggedPointerInfo(&info_bits, &value_bits)) {
date_value_bits = ((value_bits << 8) | (info_bits << 4));
memcpy(&date_value, &date_value_bits, sizeof(date_value_bits));
// Accomodate the __NSTaggedDate format introduced in Foundation 1600.
if (class_name == g___NSTaggedDate) {
auto *apple_runtime = llvm::dyn_cast_or_null<AppleObjCRuntime>(
ObjCLanguageRuntime::Get(*process_sp));
if (!apple_runtime)
return false;
if (apple_runtime->GetFoundationVersion() >= 1600)
date_value = decodeTaggedTimeInterval(value_bits << 4);
}
} else {
llvm::Triple triple(
process_sp->GetTarget().GetArchitecture().GetTriple());
@ -850,34 +890,7 @@ bool lldb_private::formatters::NSDateSummaryProvider(
} else
return false;
if (date_value == -63114076800) {
stream.Printf("0001-12-30 00:00:00 +0000");
return true;
}
// Accomodate for the __NSTaggedDate format introduced in Foundation 1600.
if (class_name == g___NSTaggedDate) {
auto *runtime = llvm::dyn_cast_or_null<AppleObjCRuntime>(
ObjCLanguageRuntime::Get(*process_sp));
if (runtime && runtime->GetFoundationVersion() >= 1600)
date_value = decodeTaggedTimeInterval(value_bits << 4);
}
// this snippet of code assumes that time_t == seconds since Jan-1-1970 this
// is generally true and POSIXly happy, but might break if a library vendor
// decides to get creative
time_t epoch = GetOSXEpoch();
epoch = epoch + (time_t)date_value;
tm *tm_date = gmtime(&epoch);
if (!tm_date)
return false;
std::string buffer(1024, 0);
if (strftime(&buffer[0], 1023, "%Z", tm_date) == 0)
return false;
stream.Printf("%04d-%02d-%02d %02d:%02d:%02d %s", tm_date->tm_year + 1900,
tm_date->tm_mon + 1, tm_date->tm_mday, tm_date->tm_hour,
tm_date->tm_min, tm_date->tm_sec, buffer.c_str());
return true;
return NSDate::FormatDateValue(date_value, stream);
}
bool lldb_private::formatters::ObjCClassSummaryProvider(

View File

@ -1,5 +1,6 @@
add_lldb_unittest(LLDBFormatterTests
FormatManagerTests.cpp
MockTests.cpp
StringPrinterTests.cpp
LINK_LIBS

View File

@ -0,0 +1,40 @@
//===-- MockTests.cpp -----------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/DataFormatters/Mock.h"
#include "lldb/Utility/StreamString.h"
#include "llvm/ADT/Optional.h"
#include "gtest/gtest.h"
#include <string>
using namespace lldb_private;
// Try to format the date_value field of a NSDate.
static llvm::Optional<std::string> formatDateValue(double date_value) {
StreamString s;
bool succcess = formatters::NSDate::FormatDateValue(date_value, s);
if (succcess)
return std::string(s.GetData());
return llvm::None;
}
TEST(DataFormatterMockTest, NSDate) {
EXPECT_EQ(*formatDateValue(-63114076800), "0001-12-30 00:00:00 +0000");
// Can't convert the date_value to a time_t.
EXPECT_EQ(formatDateValue(std::numeric_limits<time_t>::max() + 1),
llvm::None);
EXPECT_EQ(formatDateValue(std::numeric_limits<time_t>::min() - 1),
llvm::None);
// Can't add the macOS epoch to the converted date_value (the add overflows).
EXPECT_EQ(formatDateValue(std::numeric_limits<time_t>::max()), llvm::None);
EXPECT_EQ(formatDateValue(std::numeric_limits<time_t>::min()), llvm::None);
EXPECT_EQ(*formatDateValue(0), "2001-01-01 00:00:00 UTC");
}