mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 00:39:49 +00:00
[vm] De-obfuscate function and file names in DWARF sections.
Note that when generating unstripped ELF snapshots with --obfuscate, this means DWARF sections in the resulting snapshot will leak otherwise obfuscated information. To avoid this, use both --strip to strip the generated snapshot and --save-debugging-info=<...> to save the unobfuscated DWARF information to a separate file. Fixes https://github.com/dart-lang/sdk/issues/35563. Change-Id: I8795e2a5623ad5fc5362257007f677e622a7700b Cq-Include-Trybots: luci.dart.try:vm-kernel-precomp-linux-release-x64-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-precomp-mac-release-simarm_x64-try,vm-kernel-precomp-win-release-x64-try Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/127166 Commit-Queue: Teagan Strickland <sstrickl@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
parent
3389b3bd32
commit
4b8fd3c412
|
@ -648,6 +648,13 @@ static void CreateAndWritePrecompiledSnapshot() {
|
|||
/*debug_callback_data=*/nullptr);
|
||||
}
|
||||
CHECK_RESULT(result);
|
||||
if (obfuscate && !strip) {
|
||||
Syslog::PrintErr(
|
||||
"Warning: The generated ELF library contains unobfuscated DWARF "
|
||||
"debugging information.\n"
|
||||
" To avoid this, use --strip to remove it and "
|
||||
"--save-debugging-info=<...> to save it to a separate file.\n");
|
||||
}
|
||||
} else if (snapshot_kind == kAppAOTBlobs) {
|
||||
Syslog::PrintErr(
|
||||
"WARNING: app-aot-blobs snapshots have been deprecated and support for "
|
||||
|
|
|
@ -55,9 +55,53 @@ class InliningNode : public ZoneAllocated {
|
|||
InliningNode* children_next;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Trie<T>* Trie<T>::AddString(Zone* zone,
|
||||
Trie<T>* trie,
|
||||
const char* key,
|
||||
const T* value) {
|
||||
ASSERT(key != nullptr);
|
||||
if (trie == nullptr) {
|
||||
trie = new (zone) Trie<T>();
|
||||
}
|
||||
if (*key == '\0') {
|
||||
ASSERT(trie->value_ == nullptr);
|
||||
trie->value_ = value;
|
||||
} else {
|
||||
auto const index = ChildIndex(*key);
|
||||
ASSERT(index >= 0 && index < kNumValidChars);
|
||||
trie->children_[index] =
|
||||
AddString(zone, trie->children_[index], key + 1, value);
|
||||
}
|
||||
|
||||
return trie;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* Trie<T>::Lookup(const Trie<T>* trie, const char* key, intptr_t* end) {
|
||||
intptr_t i = 0;
|
||||
for (; key[i] != '\0'; i++) {
|
||||
auto const index = ChildIndex(key[i]);
|
||||
ASSERT(index < kNumValidChars);
|
||||
if (index < 0) {
|
||||
if (end == nullptr) return nullptr;
|
||||
break;
|
||||
}
|
||||
// Still find the longest valid trie prefix when no stored value.
|
||||
if (trie == nullptr) continue;
|
||||
trie = trie->children_[index];
|
||||
}
|
||||
if (end != nullptr) {
|
||||
*end = i;
|
||||
}
|
||||
if (trie == nullptr) return nullptr;
|
||||
return trie->value_;
|
||||
}
|
||||
|
||||
Dwarf::Dwarf(Zone* zone, StreamingWriteStream* stream, Elf* elf)
|
||||
: zone_(zone),
|
||||
elf_(elf),
|
||||
reverse_obfuscation_trie_(CreateReverseObfuscationTrie(zone)),
|
||||
asm_stream_(stream),
|
||||
bin_stream_(nullptr),
|
||||
codes_(zone, 1024),
|
||||
|
@ -357,8 +401,8 @@ void Dwarf::WriteAbstractFunctions() {
|
|||
const Function& function = *(functions_[i]);
|
||||
name = function.QualifiedUserVisibleName();
|
||||
script = function.script();
|
||||
intptr_t file = LookupScript(script);
|
||||
intptr_t line = 0; // Not known. Script has already lost its token stream.
|
||||
const intptr_t file = LookupScript(script);
|
||||
const intptr_t line = 0; // Unknown, script already lost its token stream.
|
||||
|
||||
if (asm_stream_) {
|
||||
Print(".Lfunc%" Pd ":\n",
|
||||
|
@ -366,12 +410,14 @@ void Dwarf::WriteAbstractFunctions() {
|
|||
} else {
|
||||
abstract_origins_[i] = position();
|
||||
}
|
||||
auto const name_cstr = Deobfuscate(name.ToCString());
|
||||
|
||||
uleb128(kAbstractFunction);
|
||||
string(name.ToCString()); // DW_AT_name
|
||||
uleb128(file); // DW_AT_decl_file
|
||||
uleb128(line); // DW_AT_decl_line
|
||||
uleb128(DW_INL_inlined); // DW_AT_inline
|
||||
uleb128(0); // End of children.
|
||||
string(name_cstr); // DW_AT_name
|
||||
uleb128(file); // DW_AT_decl_file
|
||||
uleb128(line); // DW_AT_decl_line
|
||||
uleb128(DW_INL_inlined); // DW_AT_inline
|
||||
uleb128(0); // End of children.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -637,8 +683,10 @@ void Dwarf::WriteLines() {
|
|||
for (intptr_t i = 0; i < scripts_.length(); i++) {
|
||||
const Script& script = *(scripts_[i]);
|
||||
uri = script.url();
|
||||
RELEASE_ASSERT(strlen(uri.ToCString()) != 0);
|
||||
string(uri.ToCString()); // NOLINT
|
||||
auto const uri_cstr = Deobfuscate(uri.ToCString());
|
||||
RELEASE_ASSERT(strlen(uri_cstr) != 0);
|
||||
|
||||
string(uri_cstr); // NOLINT
|
||||
uleb128(0); // Include directory index.
|
||||
uleb128(0); // File modification time.
|
||||
uleb128(0); // File length.
|
||||
|
@ -825,6 +873,50 @@ void Dwarf::WriteLines() {
|
|||
}
|
||||
}
|
||||
|
||||
const char* Dwarf::Deobfuscate(const char* cstr) {
|
||||
if (reverse_obfuscation_trie_ == nullptr) return cstr;
|
||||
TextBuffer buffer(256);
|
||||
// Used to avoid Zone-allocating strings if no deobfuscation was performed.
|
||||
bool changed = false;
|
||||
intptr_t i = 0;
|
||||
while (cstr[i] != '\0') {
|
||||
intptr_t offset;
|
||||
auto const value = reverse_obfuscation_trie_->Lookup(cstr + i, &offset);
|
||||
if (offset == 0) {
|
||||
// The first character was an invalid key element (that isn't the null
|
||||
// terminator due to the while condition), copy it and skip to the next.
|
||||
buffer.AddChar(cstr[i++]);
|
||||
} else if (value != nullptr) {
|
||||
changed = true;
|
||||
buffer.AddString(value);
|
||||
} else {
|
||||
buffer.AddRaw(reinterpret_cast<const uint8_t*>(cstr + i), offset);
|
||||
}
|
||||
i += offset;
|
||||
}
|
||||
if (!changed) return cstr;
|
||||
return OS::SCreate(zone_, "%s", buffer.buf());
|
||||
}
|
||||
|
||||
Trie<const char>* Dwarf::CreateReverseObfuscationTrie(Zone* zone) {
|
||||
auto const I = Thread::Current()->isolate();
|
||||
auto const map_array = I->obfuscation_map();
|
||||
if (map_array == nullptr) return nullptr;
|
||||
|
||||
Trie<const char>* trie = nullptr;
|
||||
for (intptr_t i = 0; map_array[i] != nullptr; i += 2) {
|
||||
auto const key = map_array[i];
|
||||
auto const value = map_array[i + 1];
|
||||
ASSERT(value != nullptr);
|
||||
// Don't include identity mappings.
|
||||
if (strcmp(key, value) == 0) continue;
|
||||
// Otherwise, any value in the obfuscation map should be a valid key.
|
||||
ASSERT(Trie<const char>::IsValidKey(value));
|
||||
trie = Trie<const char>::AddString(zone, trie, value, key);
|
||||
}
|
||||
return trie;
|
||||
}
|
||||
|
||||
#endif // DART_PRECOMPILER
|
||||
|
||||
} // namespace dart
|
||||
|
|
|
@ -117,6 +117,80 @@ struct CodeIndexPair {
|
|||
|
||||
typedef DirectChainedHashMap<CodeIndexPair> CodeIndexMap;
|
||||
|
||||
template <typename T>
|
||||
class Trie : public ZoneAllocated {
|
||||
public:
|
||||
// Returns whether [key] is a valid trie key (that is, a C string that
|
||||
// contains only characters for which charIndex returns a non-negative value).
|
||||
static bool IsValidKey(const char* key) {
|
||||
for (intptr_t i = 0; key[i] != '\0'; i++) {
|
||||
if (ChildIndex(key[i]) < 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Adds a binding of [key] to [value] in [trie]. Assumes that the string in
|
||||
// [key] is a valid trie key and does not already have a value in [trie].
|
||||
//
|
||||
// If [trie] is nullptr, then a new trie is created and a pointer to the new
|
||||
// trie is returned. Otherwise, [trie] will be returned.
|
||||
static Trie<T>* AddString(Zone* zone,
|
||||
Trie<T>* trie,
|
||||
const char* key,
|
||||
const T* value);
|
||||
|
||||
// Adds a binding of [key] to [value]. Assumes that the string in [key] is a
|
||||
// valid trie key and does not already have a value in this trie.
|
||||
void AddString(Zone* zone, const char* key, const T* value) {
|
||||
AddString(zone, this, key, value);
|
||||
}
|
||||
|
||||
// Looks up the value stored for [key] in [trie]. If one is not found, then
|
||||
// nullptr is returned.
|
||||
//
|
||||
// If [end] is not nullptr, then the longest prefix of [key] that is a valid
|
||||
// trie key prefix will be used for the lookup and the value pointed to by
|
||||
// [end] is set to the index after that prefix. Otherwise, the whole [key]
|
||||
// is used.
|
||||
static const T* Lookup(const Trie<T>* trie,
|
||||
const char* key,
|
||||
intptr_t* end = nullptr);
|
||||
|
||||
// Looks up the value stored for [key]. If one is not found, then nullptr is
|
||||
// returned.
|
||||
//
|
||||
// If [end] is not nullptr, then the longest prefix of [key] that is a valid
|
||||
// trie key prefix will be used for the lookup and the value pointed to by
|
||||
// [end] is set to the index after that prefix. Otherwise, the whole [key]
|
||||
// is used.
|
||||
const T* Lookup(const char* key, intptr_t* end = nullptr) const {
|
||||
return Lookup(this, key, end);
|
||||
}
|
||||
|
||||
private:
|
||||
// Currently, only the following characters can appear in obfuscated names:
|
||||
// '_', '@', '0-9', 'a-z', 'A-Z'
|
||||
static const intptr_t kNumValidChars = 64;
|
||||
|
||||
Trie() {
|
||||
for (intptr_t i = 0; i < kNumValidChars; i++) {
|
||||
children_[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
static intptr_t ChildIndex(char c) {
|
||||
if (c == '_') return 0;
|
||||
if (c == '@') return 1;
|
||||
if (c >= '0' && c <= '9') return ('9' - c) + 2;
|
||||
if (c >= 'a' && c <= 'z') return ('z' - c) + 12;
|
||||
if (c >= 'A' && c <= 'Z') return ('Z' - c) + 38;
|
||||
return -1;
|
||||
}
|
||||
|
||||
const T* value_ = nullptr;
|
||||
Trie<T>* children_[kNumValidChars];
|
||||
};
|
||||
|
||||
class Dwarf : public ZoneAllocated {
|
||||
public:
|
||||
Dwarf(Zone* zone, StreamingWriteStream* stream, Elf* elf);
|
||||
|
@ -302,8 +376,12 @@ class Dwarf : public ZoneAllocated {
|
|||
AssemblyCodeNamer* namer);
|
||||
void WriteLines();
|
||||
|
||||
const char* Deobfuscate(const char* cstr);
|
||||
static Trie<const char>* CreateReverseObfuscationTrie(Zone* zone);
|
||||
|
||||
Zone* const zone_;
|
||||
Elf* const elf_;
|
||||
Trie<const char>* const reverse_obfuscation_trie_;
|
||||
StreamingWriteStream* asm_stream_;
|
||||
WriteStream* bin_stream_;
|
||||
ZoneGrowableArray<const Code*> codes_;
|
||||
|
|
79
tests/standalone_2/dwarf_stack_trace_obfuscate_test.dart
Normal file
79
tests/standalone_2/dwarf_stack_trace_obfuscate_test.dart
Normal file
|
@ -0,0 +1,79 @@
|
|||
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
/// VMOptions=--dwarf-stack-traces --save-debugging-info=dwarf_obfuscate.so --obfuscate
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:vm/dwarf/dwarf.dart';
|
||||
|
||||
import 'dwarf_stack_trace_test.dart' as base;
|
||||
|
||||
@pragma("vm:prefer-inline")
|
||||
bar() {
|
||||
// Keep the 'throw' and its argument on separate lines.
|
||||
throw // force linebreak with dartfmt
|
||||
"Hello, Dwarf!";
|
||||
}
|
||||
|
||||
@pragma("vm:never-inline")
|
||||
foo() {
|
||||
bar();
|
||||
}
|
||||
|
||||
Future<void> main() async {
|
||||
String rawStack;
|
||||
try {
|
||||
foo();
|
||||
} catch (e, st) {
|
||||
rawStack = st.toString();
|
||||
}
|
||||
|
||||
if (path.basenameWithoutExtension(Platform.executable) !=
|
||||
"dart_precompiled_runtime") {
|
||||
return; // Not running from an AOT compiled snapshot.
|
||||
}
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
return; // Generated dwarf.so not available on the test device.
|
||||
}
|
||||
|
||||
final dwarf = Dwarf.fromFile("dwarf_obfuscate.so");
|
||||
|
||||
await base.checkStackTrace(rawStack, dwarf, expectedCallsInfo);
|
||||
}
|
||||
|
||||
final expectedCallsInfo = <List<CallInfo>>[
|
||||
// The first frame should correspond to the throw in bar, which was inlined
|
||||
// into foo (so we'll get information for two calls for that PC address).
|
||||
[
|
||||
CallInfo(
|
||||
function: "bar",
|
||||
filename: "dwarf_stack_trace_obfuscate_test.dart",
|
||||
line: 17,
|
||||
inlined: true),
|
||||
CallInfo(
|
||||
function: "foo",
|
||||
filename: "dwarf_stack_trace_obfuscate_test.dart",
|
||||
line: 23,
|
||||
inlined: false)
|
||||
],
|
||||
// The second frame corresponds to call to foo in main.
|
||||
[
|
||||
CallInfo(
|
||||
function: "main",
|
||||
filename: "dwarf_stack_trace_obfuscate_test.dart",
|
||||
line: 29,
|
||||
inlined: false)
|
||||
],
|
||||
// Internal frames have non-positive line numbers in the call information.
|
||||
[
|
||||
CallInfo(
|
||||
function: "main",
|
||||
filename: "dwarf_stack_trace_obfuscate_test.dart",
|
||||
line: 0,
|
||||
inlined: false),
|
||||
]
|
||||
];
|
|
@ -7,10 +7,10 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:unittest/unittest.dart';
|
||||
import 'package:vm/dwarf/convert.dart';
|
||||
import 'package:vm/dwarf/dwarf.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
@pragma("vm:prefer-inline")
|
||||
bar() {
|
||||
|
@ -32,14 +32,8 @@ Future<void> main() async {
|
|||
rawStack = st.toString();
|
||||
}
|
||||
|
||||
// Check that our expected information is consistent.
|
||||
checkConsistency(expectedExternalCallInfo, expectedAllCallsInfo);
|
||||
|
||||
print("");
|
||||
print("Raw stack trace:");
|
||||
print(rawStack);
|
||||
|
||||
if (!Platform.executable.endsWith("dart_precompiled_runtime")) {
|
||||
if (path.basenameWithoutExtension(Platform.executable) !=
|
||||
"dart_precompiled_runtime") {
|
||||
return; // Not running from an AOT compiled snapshot.
|
||||
}
|
||||
|
||||
|
@ -49,6 +43,18 @@ Future<void> main() async {
|
|||
|
||||
final dwarf = Dwarf.fromFile("dwarf.so");
|
||||
|
||||
await checkStackTrace(rawStack, dwarf, expectedCallsInfo);
|
||||
}
|
||||
|
||||
Future<void> checkStackTrace(String rawStack, Dwarf dwarf,
|
||||
List<List<CallInfo>> expectedCallsInfo) async {
|
||||
final expectedAllCallsInfo = expectedCallsInfo;
|
||||
final expectedExternalCallInfo = removeInternalCalls(expectedCallsInfo);
|
||||
|
||||
print("");
|
||||
print("Raw stack trace:");
|
||||
print(rawStack);
|
||||
|
||||
var rawLines =
|
||||
await Stream.value(rawStack).transform(const LineSplitter()).toList();
|
||||
|
||||
|
@ -131,7 +137,7 @@ Future<void> main() async {
|
|||
expect(allCallsTrace, stringContainsInOrder(expectedStrings));
|
||||
}
|
||||
|
||||
final expectedExternalCallInfo = <List<CallInfo>>[
|
||||
final expectedCallsInfo = <List<CallInfo>>[
|
||||
// The first frame should correspond to the throw in bar, which was inlined
|
||||
// into foo (so we'll get information for two calls for that PC address).
|
||||
[
|
||||
|
@ -140,13 +146,13 @@ final expectedExternalCallInfo = <List<CallInfo>>[
|
|||
filename: "dwarf_stack_trace_test.dart",
|
||||
line: 18,
|
||||
inlined: true),
|
||||
// The second frame corresponds to call to foo in main.
|
||||
CallInfo(
|
||||
function: "foo",
|
||||
filename: "dwarf_stack_trace_test.dart",
|
||||
line: 24,
|
||||
inlined: false)
|
||||
],
|
||||
// The second frame corresponds to call to foo in main.
|
||||
[
|
||||
CallInfo(
|
||||
function: "main",
|
||||
|
@ -154,22 +160,20 @@ final expectedExternalCallInfo = <List<CallInfo>>[
|
|||
line: 30,
|
||||
inlined: false)
|
||||
],
|
||||
// No call information for the main tearoff.
|
||||
[],
|
||||
// Internal frames have non-positive line numbers in the call information.
|
||||
[
|
||||
CallInfo(
|
||||
function: "main",
|
||||
filename: "dwarf_stack_trace_test.dart",
|
||||
line: 0,
|
||||
inlined: false),
|
||||
]
|
||||
];
|
||||
|
||||
// Replace the call information for the main tearoff frame.
|
||||
final expectedAllCallsInfo = expectedExternalCallInfo.sublist(0, 2) +
|
||||
<List<CallInfo>>[
|
||||
// Internal frames have non-positive line numbers in the call information.
|
||||
[
|
||||
CallInfo(
|
||||
function: "main",
|
||||
filename: "dwarf_stack_trace_test.dart",
|
||||
line: 0,
|
||||
inlined: false),
|
||||
]
|
||||
];
|
||||
List<List<CallInfo>> removeInternalCalls(List<List<CallInfo>> original) =>
|
||||
original
|
||||
.map((frame) => frame.where((call) => call.line > 0).toList())
|
||||
.toList();
|
||||
|
||||
void checkConsistency(
|
||||
List<List<CallInfo>> externalFrames, List<List<CallInfo>> allFrames) {
|
||||
|
|
Loading…
Reference in a new issue