Skip to content

Commit

Permalink
Merge pull request #1637 from yingcong-wu/yc/devicesan/fix-free-probl…
Browse files Browse the repository at this point in the history
…ems-0517

[DeviceSanitizer] refactor options handling and fix use-after-free related problems
  • Loading branch information
callumfare committed Jun 26, 2024
2 parents b854c86 + 8bf5596 commit 45c3429
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 88 deletions.
78 changes: 15 additions & 63 deletions source/loader/layers/sanitizer/asan_interceptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/

#include "asan_interceptor.hpp"
#include "asan_options.hpp"
#include "asan_quarantine.hpp"
#include "asan_report.hpp"
#include "asan_shadow_setup.hpp"
Expand Down Expand Up @@ -143,65 +144,9 @@ ur_result_t enqueueMemSetShadow(ur_context_handle_t Context,
} // namespace

SanitizerInterceptor::SanitizerInterceptor() {
auto Options = getenv_to_map("UR_LAYER_ASAN_OPTIONS");
if (!Options.has_value()) {
return;
}

auto KV = Options->find("debug");
if (KV != Options->end()) {
auto Value = KV->second.front();
cl_Debug = Value == "1" || Value == "true" ? 1 : 0;
}

KV = Options->find("redzone");
if (KV != Options->end()) {
auto Value = KV->second.front();
try {
cl_MinRZSize = std::stoul(Value);
if (cl_MinRZSize < 16) {
cl_MinRZSize = 16;
context.logger.warning("Trying to set redzone size to a value "
"less than 16 is ignored");
}
} catch (...) {
die("<SANITIZER>[ERROR]: \"redzone\" should be an integer");
}
}
KV = Options->find("max_redzone");
if (KV != Options->end()) {
auto Value = KV->second.front();
try {
cl_MaxRZSize = std::stoul(Value);
if (cl_MaxRZSize > 2048) {
cl_MaxRZSize = 2048;
context.logger.warning("Trying to set max redzone size to a "
"value greater than 2048 is ignored");
}
} catch (...) {
die("<SANITIZER>[ERROR]: \"max_redzone\" should be an integer");
}
}

KV = Options->find("quarantine_size_mb");
if (KV != Options->end()) {
auto Value = KV->second.front();
try {
cl_MaxQuarantineSizeMB = std::stoul(Value);
} catch (...) {
die("<SANITIZER>[ERROR]: \"cl_MaxQuarantineSizeMB\" should be an "
"integer");
}
}
if (cl_MaxQuarantineSizeMB) {
m_Quarantine =
std::make_unique<Quarantine>(cl_MaxQuarantineSizeMB * 1024 * 1024);
}

KV = Options->find("detect_locals");
if (KV != Options->end()) {
auto Value = KV->second.front();
cl_DetectLocals = Value == "1" || Value == "true" ? true : false;
if (Options().MaxQuarantineSizeMB) {
m_Quarantine = std::make_unique<Quarantine>(
static_cast<uint64_t>(Options().MaxQuarantineSizeMB) * 1024 * 1024);
}
}

Expand Down Expand Up @@ -241,7 +186,7 @@ ur_result_t SanitizerInterceptor::allocateMemory(
Alignment = MinAlignment;
}

uptr RZLog = ComputeRZLog(Size, cl_MinRZSize, cl_MaxRZSize);
uptr RZLog = ComputeRZLog(Size, Options().MinRZSize, Options().MaxRZSize);
uptr RZSize = RZLog2Size(RZLog);
uptr RoundedSize = RoundUpTo(Size, Alignment);
uptr NeededSize = RoundedSize + RZSize * 2;
Expand Down Expand Up @@ -746,7 +691,9 @@ ur_result_t SanitizerInterceptor::prepareLaunch(
};

// Write debug
EnqueueWriteGlobal(kSPIR_AsanDebug, &cl_Debug, sizeof(cl_Debug));
// We use "uint64_t" here because EnqueueWriteGlobal will fail when it's "uint32_t"
uint64_t Debug = Options().Debug ? 1 : 0;
EnqueueWriteGlobal(kSPIR_AsanDebug, &Debug, sizeof(Debug));

// Write shadow memory offset for global memory
EnqueueWriteGlobal(kSPIR_AsanShadowMemoryGlobalStart,
Expand Down Expand Up @@ -806,7 +753,7 @@ ur_result_t SanitizerInterceptor::prepareLaunch(
};

// Write shadow memory offset for local memory
if (cl_DetectLocals) {
if (Options().DetectLocals) {
// CPU needn't this
if (DeviceInfo->Type == DeviceType::GPU_PVC) {
size_t LocalMemorySize = GetLocalMemorySize(DeviceInfo->Handle);
Expand Down Expand Up @@ -843,7 +790,12 @@ SanitizerInterceptor::findAllocInfoByAddress(uptr Address) {
if (It == m_AllocationMap.begin()) {
return std::optional<AllocationIterator>{};
}
return --It;
--It;
// Make sure we got the right AllocInfo
assert(Address >= It->second->AllocBegin &&
Address < It->second->AllocBegin + It->second->AllocSize &&
"Wrong AllocInfo for the address");
return It;
}

ur_result_t USMLaunchInfo::initialize() {
Expand Down
7 changes: 0 additions & 7 deletions source/loader/layers/sanitizer/asan_interceptor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -260,13 +260,6 @@ class SanitizerInterceptor {
AllocationMap m_AllocationMap;
ur_shared_mutex m_AllocationMapMutex;

// We use "uint64_t" here because EnqueueWriteGlobal will fail when it's "uint32_t"
uint64_t cl_Debug = 0;
uint64_t cl_MinRZSize = 16;
uint64_t cl_MaxRZSize = 2048;
uint32_t cl_MaxQuarantineSizeMB = 0;
bool cl_DetectLocals = true;

std::unique_ptr<Quarantine> m_Quarantine;
};

Expand Down
146 changes: 146 additions & 0 deletions source/loader/layers/sanitizer/asan_options.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
*
* Copyright (C) 2024 Intel Corporation
*
* Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
* See LICENSE.TXT
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*
* @file asan_options.hpp
*
*/

#pragma once

#include "common/ur_util.hpp"
#include "ur/ur.hpp"
#include "ur_sanitizer_layer.hpp"

#include <algorithm>
#include <cstring>
#include <stdexcept>

namespace ur_sanitizer_layer {

struct AsanOptions {
public:
AsanOptions(AsanOptions &other) = delete;
void operator=(const AsanOptions &) = delete;

static AsanOptions &getInstance() {
static AsanOptions instance;
return instance;
}

bool Debug = false;
uint64_t MinRZSize = 16;
uint64_t MaxRZSize = 2048;
uint32_t MaxQuarantineSizeMB = 0;
bool DetectLocals = true;

private:
AsanOptions() {
auto OptionsEnvMap = getenv_to_map("UR_LAYER_ASAN_OPTIONS");
if (!OptionsEnvMap.has_value()) {
return;
}

const char *TrueStrings[] = {"1", "true"};
const char *FalseStrings[] = {"0", "false"};

auto InplaceToLower = [](std::string &S) {
std::transform(S.begin(), S.end(), S.begin(),
[](unsigned char C) { return std::tolower(C); });
};
auto IsTrue = [&](const std::string &S) {
return std::any_of(std::begin(TrueStrings), std::end(TrueStrings),
[&](const char *CS) { return S == CS; });
};
auto IsFalse = [&](const std::string &S) {
return std::any_of(std::begin(FalseStrings), std::end(FalseStrings),
[&](const char *CS) { return S == CS; });
};

auto SetBoolOption = [&](const std::string &Name, bool &Opt) {
auto KV = OptionsEnvMap->find(Name);
if (KV != OptionsEnvMap->end()) {
auto Value = KV->second.front();
InplaceToLower(Value);
if (IsTrue(Value)) {
Opt = true;
} else if (IsFalse(Value)) {
Opt = false;
} else {
std::stringstream SS;
SS << "<SANITIZER>[ERROR]: \"" << Name << "\" is set to \""
<< Value << "\", which is not an valid setting. ";
SS << "Acceptable input are: for enable, use:";
for (auto &S : TrueStrings) {
SS << " \"" << S << "\"";
}
SS << "; ";
SS << "for disable, use:";
for (auto &S : FalseStrings) {
SS << " \"" << S << "\"";
}
SS << ".";
die(SS.str().c_str());
}
}
};

SetBoolOption("debug", Debug);
SetBoolOption("detect_locals", DetectLocals);

auto KV = OptionsEnvMap->find("quarantine_size_mb");
if (KV != OptionsEnvMap->end()) {
auto Value = KV->second.front();
try {
auto temp_long = std::stoul(Value);
if (temp_long > UINT32_MAX) {
throw std::out_of_range("");
}
MaxQuarantineSizeMB = temp_long;
} catch (...) {
die("<SANITIZER>[ERROR]: \"quarantine_size_mb\" should be "
"an positive integer that smaller than or equal to "
"4294967295.");
}
}

KV = OptionsEnvMap->find("redzone");
if (KV != OptionsEnvMap->end()) {
auto Value = KV->second.front();
try {
MinRZSize = std::stoul(Value);
if (MinRZSize < 16) {
MinRZSize = 16;
context.logger.warning("Trying to set redzone size to a "
"value less than 16 is ignored");
}
} catch (...) {
die("<SANITIZER>[ERROR]: \"redzone\" should be an integer");
}
}

KV = OptionsEnvMap->find("max_redzone");
if (KV != OptionsEnvMap->end()) {
auto Value = KV->second.front();
try {
MaxRZSize = std::stoul(Value);
if (MaxRZSize > 2048) {
MaxRZSize = 2048;
context.logger.warning(
"Trying to set max redzone size to a "
"value greater than 2048 is ignored");
}
} catch (...) {
die("<SANITIZER>[ERROR]: \"max_redzone\" should be an integer");
}
}
}
};

inline const AsanOptions &Options() { return AsanOptions::getInstance(); }

} // namespace ur_sanitizer_layer
45 changes: 27 additions & 18 deletions source/loader/layers/sanitizer/asan_report.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

#include "asan_report.hpp"
#include "asan_options.hpp"

#include "asan_allocator.hpp"
#include "asan_interceptor.hpp"
Expand Down Expand Up @@ -131,27 +132,35 @@ void ReportUseAfterFree(const DeviceSanitizerReport &Report,
context.logger.always(" #0 {} {}:{}", Func, File, Report.Line);
context.logger.always("");

auto AllocInfoItOp =
context.interceptor->findAllocInfoByAddress(Report.Address);
if (!AllocInfoItOp) {
context.logger.always("Failed to find which chunck {} is allocated",
(void *)Report.Address);
} else {
auto &AllocInfo = (*AllocInfoItOp)->second;
if (AllocInfo->Context != Context) {
if (Options().MaxQuarantineSizeMB > 0) {
auto AllocInfoItOp =
context.interceptor->findAllocInfoByAddress(Report.Address);

if (!AllocInfoItOp) {
context.logger.always("Failed to find which chunck {} is allocated",
(void *)Report.Address);
} else {
auto &AllocInfo = (*AllocInfoItOp)->second;
if (AllocInfo->Context != Context) {
context.logger.always(
"Failed to find which chunck {} is allocated",
(void *)Report.Address);
}
assert(AllocInfo->IsReleased);

context.logger.always(
"{} is located inside of {} region [{}, {})",
(void *)Report.Address, ToString(AllocInfo->Type),
(void *)AllocInfo->UserBegin, (void *)AllocInfo->UserEnd);
context.logger.always("allocated here:");
AllocInfo->AllocStack.print();
context.logger.always("released here:");
AllocInfo->ReleaseStack.print();
}
assert(AllocInfo->IsReleased);

context.logger.always("{} is located inside of {} region [{}, {})",
(void *)Report.Address, ToString(AllocInfo->Type),
(void *)AllocInfo->UserBegin,
(void *)AllocInfo->UserEnd);
context.logger.always("allocated here:");
AllocInfo->AllocStack.print();
context.logger.always("released here:");
AllocInfo->ReleaseStack.print();
} else {
context.logger.always(
"Please enable quarantine to get more information like memory "
"chunck's kind and where the chunck was allocated and released.");
}

exit(1);
Expand Down

0 comments on commit 45c3429

Please sign in to comment.