Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(589)

Unified Diff: runtime/vm/precompiler.cc

Issue 3003583002: [VM, Precompiler] PoC Obfuscator (Closed)
Patch Set: Fix bad refactoring in NewAtomicRename Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « runtime/vm/precompiler.h ('k') | runtime/vm/raw_object.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: runtime/vm/precompiler.cc
diff --git a/runtime/vm/precompiler.cc b/runtime/vm/precompiler.cc
index 44b1c3ec04b442c2cc751db14b15dd08ed726655..55cd062e9739cbe998a5f1c54bb5d953f8854937 100644
--- a/runtime/vm/precompiler.cc
+++ b/runtime/vm/precompiler.cc
@@ -45,6 +45,7 @@
#include "vm/timeline.h"
#include "vm/timer.h"
#include "vm/type_table.h"
+#include "vm/unicode.h"
#include "vm/version.h"
namespace dart {
@@ -489,6 +490,7 @@ void Precompiler::DoCompileAll(
BindStaticCalls();
SwitchICCalls();
+ Obfuscate();
ProgramVisitor::Dedup();
@@ -610,30 +612,7 @@ void Precompiler::PrecompileConstructors() {
}
}
-void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) {
- // Note that <rootlibrary>.main is not a root. The appropriate main will be
- // discovered through _getMainClosure.
-
- AddSelector(Symbols::NoSuchMethod());
-
- AddSelector(Symbols::Call()); // For speed, not correctness.
-
- // Allocated from C++.
- Class& cls = Class::Handle(Z);
- for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) {
- ASSERT(isolate()->class_table()->IsValidIndex(cid));
- if (!isolate()->class_table()->HasValidClassAt(cid)) {
- continue;
- }
- if ((cid == kDynamicCid) || (cid == kVoidCid) ||
- (cid == kFreeListElement) || (cid == kForwardingCorpse)) {
- continue;
- }
- cls = isolate()->class_table()->At(cid);
- AddInstantiatedClass(cls);
- }
-
- Dart_QualifiedFunctionName vm_entry_points[] = {
+static Dart_QualifiedFunctionName vm_entry_points[] = {
// Functions
{"dart:core", "::", "_completeDeferredLoads"},
{"dart:core", "::", "identityHashCode"},
@@ -673,9 +652,33 @@ void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) {
#endif // !PRODUCT
// Fields
{"dart:core", "Error", "_stackTrace"},
+ {"dart:core", "::", "_uriBaseClosure"},
{"dart:math", "_Random", "_state"},
{NULL, NULL, NULL} // Must be terminated with NULL entries.
- };
+};
+
+void Precompiler::AddRoots(Dart_QualifiedFunctionName embedder_entry_points[]) {
+ // Note that <rootlibrary>.main is not a root. The appropriate main will be
+ // discovered through _getMainClosure.
+
+ AddSelector(Symbols::NoSuchMethod());
+
+ AddSelector(Symbols::Call()); // For speed, not correctness.
+
+ // Allocated from C++.
+ Class& cls = Class::Handle(Z);
+ for (intptr_t cid = kInstanceCid; cid < kNumPredefinedCids; cid++) {
+ ASSERT(isolate()->class_table()->IsValidIndex(cid));
+ if (!isolate()->class_table()->HasValidClassAt(cid)) {
+ continue;
+ }
+ if ((cid == kDynamicCid) || (cid == kVoidCid) ||
+ (cid == kFreeListElement) || (cid == kForwardingCorpse)) {
+ continue;
+ }
+ cls = isolate()->class_table()->At(cid);
+ AddInstantiatedClass(cls);
+ }
AddEntryPoints(vm_entry_points);
AddEntryPoints(embedder_entry_points);
@@ -2278,6 +2281,82 @@ void Precompiler::SwitchICCalls() {
#endif
}
+void Precompiler::Obfuscate() {
+ if (!I->obfuscate()) {
+ return;
+ }
+
+ class ScriptsCollector : public ObjectVisitor {
+ public:
+ explicit ScriptsCollector(Zone* zone,
+ GrowableHandlePtrArray<const Script>* scripts)
+ : script_(Script::Handle(zone)), scripts_(scripts) {}
+
+ void VisitObject(RawObject* obj) {
+ if (obj->GetClassId() == kScriptCid) {
+ script_ ^= obj;
+ scripts_->Add(Script::Cast(script_));
+ }
+ }
+
+ private:
+ Script& script_;
+ GrowableHandlePtrArray<const Script>* scripts_;
+ };
+
+ GrowableHandlePtrArray<const Script> scripts(Z, 100);
+ Isolate::Current()->heap()->CollectAllGarbage();
+ {
+ HeapIterationScope his(T);
+ ScriptsCollector visitor(Z, &scripts);
+ I->heap()->VisitObjects(&visitor);
+ }
+
+ {
+ // Note: when this object is destroyed it will commit obfuscation
+ // mappings into the ObjectStore. Hence the block around it - to
+ // ensure that destructor is called before we save obfuscation
+ // mappings and clear the ObjectStore.
+ Obfuscator obfuscator(T, /*private_key=*/String::Handle(Z));
+ String& str = String::Handle(Z);
+ for (intptr_t i = 0; i < scripts.length(); i++) {
+ const Script& script = scripts.At(i);
+
+ str = script.url();
+ str = Symbols::New(T, str);
+ str = obfuscator.Rename(str, /*atomic=*/true);
+ script.set_url(str);
+
+ str = script.resolved_url();
+ str = Symbols::New(T, str);
+ str = obfuscator.Rename(str, /*atomic=*/true);
+ script.set_resolved_url(str);
+ }
+
+ Library& lib = Library::Handle();
+ for (intptr_t i = 0; i < libraries_.Length(); i++) {
+ lib ^= libraries_.At(i);
+ if (!lib.is_dart_scheme()) {
+ str = lib.name();
+ str = obfuscator.Rename(str, /*atomic=*/true);
+ lib.set_name(str);
+
+ str = lib.url();
+ str = Symbols::New(T, str);
+ str = obfuscator.Rename(str, /*atomic=*/true);
+ lib.set_url(str);
+ }
+ }
+ Library::RegisterLibraries(T, libraries_);
+ }
+
+ // Obfuscation is done. Move obfuscation map into malloced memory.
+ I->set_obfuscation_map(Obfuscator::SerializeMap(T));
+
+ // Discard obfuscation mappings to avoid including them into snapshot.
+ I->object_store()->set_obfuscation_map(Array::Handle(Z));
+}
+
void Precompiler::FinalizeAllClasses() {
Library& lib = Library::Handle(Z);
Class& cls = Class::Handle(Z);
@@ -3330,6 +3409,426 @@ RawError* Precompiler::CompileFunction(Precompiler* precompiler,
return PrecompileFunctionHelper(precompiler, &pipeline, function, optimized);
}
+Obfuscator::Obfuscator(Thread* thread, const String& private_key)
+ : state_(NULL) {
+ Isolate* isolate = thread->isolate();
+ Zone* zone = thread->zone();
+ if (!isolate->obfuscate()) {
+ // Nothing to do.
+ return;
+ }
+
+ // Create ObfuscationState from ObjectStore::obfusction_map().
+ ObjectStore* store = thread->isolate()->object_store();
+ Array& obfuscation_state = Array::Handle(zone, store->obfuscation_map());
+
+ if (store->obfuscation_map() == Array::null()) {
+ // We are just starting the obfuscation. Create initial state.
+ const int kInitialPrivateCapacity = 256;
+ obfuscation_state = Array::New(kSavedStateSize);
+ obfuscation_state.SetAt(
+ 1, Array::Handle(zone, HashTables::New<ObfuscationMap>(
+ kInitialPrivateCapacity, Heap::kOld)));
+ }
+
+ state_ = new (zone) ObfuscationState(thread, obfuscation_state, private_key);
+
+ if (store->obfuscation_map() == Array::null()) {
+ // We are just starting the obfuscation. Initialize the renaming map.
+ // Note: InitializeRenamingMap uses state_.
+ InitializeRenamingMap(isolate);
+ }
+}
+
+Obfuscator::~Obfuscator() {
+ if (state_ != NULL) {
+ state_->SaveState();
+ }
+}
+
+void Obfuscator::InitializeRenamingMap(Isolate* isolate) {
+ // Prevent renaming of classes and method names mentioned in the
+ // entry points lists.
+ PreventRenaming(vm_entry_points);
+ PreventRenaming(isolate->embedder_entry_points());
+
+// Prevent renaming of all pseudo-keywords and operators.
+// Note: not all pseudo-keywords are mentioned in DART_KEYWORD_LIST
+// (for example 'hide', 'show' and async related keywords are omitted).
+// Those are protected from renaming as part of all symbols.
+#define PREVENT_RENAMING(name, value, priority, attr) \
+ do { \
+ if (Token::CanBeOverloaded(Token::name) || \
+ ((Token::attr & Token::kPseudoKeyword) != 0)) { \
+ PreventRenaming(value); \
+ } \
+ } while (0);
+
+ DART_TOKEN_LIST(PREVENT_RENAMING)
+ DART_KEYWORD_LIST(PREVENT_RENAMING)
+#undef PREVENT_RENAMING
+
+ // this is a keyword token unless it occurs in the string interpolation
+ // which causes it to be obfuscated.
+ PreventRenaming("this");
+
+// Protect all symbols from renaming.
+#define PREVENT_RENAMING(name, value) PreventRenaming(value);
+ PREDEFINED_SYMBOLS_LIST(PREVENT_RENAMING)
+#undef PREVENT_RENAMING
+
+ // Protect NativeFieldWrapperClassX names from being obfuscated. Those
+ // classes are created manually by the runtime system.
+ // TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place
+ // where these are created.
+ PreventRenaming("NativeFieldWrapperClass1");
+ PreventRenaming("NativeFieldWrapperClass2");
+ PreventRenaming("NativeFieldWrapperClass3");
+ PreventRenaming("NativeFieldWrapperClass4");
+
+// Prevent renaming of ClassID.cid* fields. These fields are injected by
+// runtime.
+// TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place
+// where these are created.
+#define CLASS_LIST_WITH_NULL(V) \
+ V(Null) \
+ CLASS_LIST_NO_OBJECT(V)
+#define PREVENT_RENAMING(clazz) PreventRenaming("cid" #clazz);
+ CLASS_LIST_WITH_NULL(PREVENT_RENAMING)
+#undef PREVENT_RENAMING
+#undef CLASS_LIST_WITH_NULL
+
+// Prevent renaming of methods that are looked up by method recognizer.
+// TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place
+// where these are looked up.
+#define PREVENT_RENAMING(class_name, function_name, recognized_enum, \
+ result_type, fingerprint) \
+ do { \
+ PreventRenaming(#class_name); \
+ PreventRenaming(#function_name); \
+ } while (0);
+ RECOGNIZED_LIST(PREVENT_RENAMING)
+#undef PREVENT_RENAMING
+
+// Prevent renaming of methods that are looked up by method recognizer.
+// TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place
+// where these are looked up.
+#define PREVENT_RENAMING(class_name, function_name, recognized_enum, \
+ fingerprint) \
+ do { \
+ PreventRenaming(#class_name); \
+ PreventRenaming(#function_name); \
+ } while (0);
+ INLINE_WHITE_LIST(PREVENT_RENAMING)
+ INLINE_BLACK_LIST(PREVENT_RENAMING)
+ POLYMORPHIC_TARGET_LIST(PREVENT_RENAMING)
+#undef PREVENT_RENAMING
+
+ // These are not mentioned by entry points but are still looked up by name.
+ // (They are not mentioned in the entry points because we don't need them
+ // after the compilation)
+ PreventRenaming("_resolveScriptUri");
+
+ // Precompiler is looking up "main".
+ // TODO(dartbug.com/30524) instead call to Obfuscator::Rename from a place
+ // where these are created.
+ PreventRenaming("main");
+
+ // Fast path for common conditional import. See Deobfuscate method.
+ PreventRenaming("dart");
+ PreventRenaming("library");
+ PreventRenaming("io");
+ PreventRenaming("html");
+}
+
+RawString* Obfuscator::ObfuscationState::RenameImpl(const String& name,
+ bool atomic) {
+ ASSERT(name.IsSymbol());
+
+ renamed_ ^= renames_.GetOrNull(name);
+ if (renamed_.IsNull()) {
+ renamed_ = BuildRename(name, atomic);
+ renames_.UpdateOrInsert(name, renamed_);
+ }
+ return renamed_.raw();
+}
+
+void Obfuscator::PreventRenaming(Dart_QualifiedFunctionName entry_points[]) {
+ for (intptr_t i = 0; entry_points[i].function_name != NULL; i++) {
+ const char* class_name = entry_points[i].class_name;
+ const char* function_name = entry_points[i].function_name;
+
+ const size_t class_name_len = strlen(class_name);
+ if (strncmp(function_name, class_name, class_name_len) == 0 &&
+ function_name[class_name_len] == '.') {
+ const char* ctor_name = function_name + class_name_len + 1;
+ if (ctor_name[0] != '\0') {
+ PreventRenaming(ctor_name);
+ }
+ } else {
+ PreventRenaming(function_name);
+ }
+ PreventRenaming(class_name);
+ }
+}
+
+static const char* const kGetterPrefix = "get:";
+static const intptr_t kGetterPrefixLength = strlen(kGetterPrefix);
+static const char* const kSetterPrefix = "set:";
+static const intptr_t kSetterPrefixLength = strlen(kSetterPrefix);
+
+void Obfuscator::PreventRenaming(const char* name) {
+ // For constructor names Class.name skip class name (if any) and a dot.
+ const char* dot = strchr(name, '.');
+ if (dot != NULL) {
+ name = dot + 1;
+ }
+
+ // Empty name: do nothing.
+ if (name[0] == '\0') {
+ return;
+ }
+
+ // Skip get: and set: prefixes.
+ if (strncmp(name, kGetterPrefix, kGetterPrefixLength) == 0) {
+ name = name + kGetterPrefixLength;
+ } else if (strncmp(name, kSetterPrefix, kSetterPrefixLength) == 0) {
+ name = name + kSetterPrefixLength;
+ }
+
+ state_->PreventRenaming(name);
+}
+
+void Obfuscator::ObfuscationState::SaveState() {
+ saved_state_.SetAt(kSavedStateNameIndex, String::Handle(String::New(name_)));
+ saved_state_.SetAt(kSavedStateRenamesIndex, renames_.Release());
+ thread_->isolate()->object_store()->set_obfuscation_map(saved_state_);
+}
+
+void Obfuscator::ObfuscationState::PreventRenaming(const char* name) {
+ string_ = Symbols::New(thread_, name);
+ PreventRenaming(string_);
+}
+
+void Obfuscator::ObfuscationState::PreventRenaming(const String& name) {
+ renames_.UpdateOrInsert(name, name);
+}
+
+void Obfuscator::ObfuscationState::NextName() {
+ // We apply the following rules:
+ //
+ // inc(a) = b, ... , inc(z) = A, ..., inc(Z) = a & carry.
+ //
+ for (intptr_t i = 0;; i++) {
+ const char digit = name_[i];
+ if (digit == '\0') {
+ name_[i] = 'a';
+ } else if (digit < 'Z') {
+ name_[i]++;
+ } else if (digit == 'Z') {
+ name_[i] = 'a';
+ continue; // Carry.
+ } else if (digit < 'z') {
+ name_[i]++;
+ } else {
+ name_[i] = 'A';
+ }
+ break;
+ }
+}
+
+RawString* Obfuscator::ObfuscationState::NewAtomicRename(
+ bool should_be_private) {
+ do {
+ NextName();
+ renamed_ = Symbols::NewFormatted(thread_, "%s%s",
+ should_be_private ? "_" : "", name_);
+ // Must check if our generated name clashes with something that will
+ // have an identity renaming.
+ } while (renames_.GetOrNull(renamed_) == renamed_.raw());
+ return renamed_.raw();
+}
+
+RawString* Obfuscator::ObfuscationState::BuildRename(const String& name,
+ bool atomic) {
+ const bool is_private = name.CharAt(0) == '_';
+ if (!atomic && is_private) {
+ // Find the first '@'.
+ intptr_t i = 0;
+ while (i < name.Length() && name.CharAt(i) != '@') {
+ i++;
+ }
+ const intptr_t end = i;
+
+ // Follow the rule:
+ //
+ // Rename(_ident@key) = Rename(_ident)@private_key_.
+ //
+ string_ = Symbols::New(thread_, name, 0, end);
+ string_ = RenameImpl(string_, /*atomic=*/true);
+ return Symbols::FromConcat(thread_, string_, private_key_);
+ } else {
+ return NewAtomicRename(is_private);
+ }
+}
+
+void Obfuscator::ObfuscateSymbolInstance(Thread* thread,
+ const Instance& symbol) {
+ // Note: this must match dart:internal.Symbol declaration.
+ const intptr_t kSymbolNameOffset = kWordSize;
+
+ Object& name_value = String::Handle();
+ name_value = symbol.RawGetFieldAtOffset(kSymbolNameOffset);
+ if (!name_value.IsString()) {
+ // dart:internal.Symbol constructor does not validate its input.
+ return;
+ }
+
+ String& name = String::Handle();
+ name ^= name_value.raw();
+
+ // TODO(vegorov) it is quite wasteful to create an obfuscator per-symbol.
+ Obfuscator obfuscator(thread, /*private_key=*/String::Handle());
+
+ // Symbol can be a sequence of identifiers separated by dots.
+ // We split such symbols into components and obfuscate individual identifiers
+ // separately.
+ String& component = String::Handle();
+ GrowableHandlePtrArray<const String> renamed(thread->zone(), 2);
+
+ const intptr_t length = name.Length();
+ intptr_t i = 0, start = 0;
+ while (i < length) {
+ // First look for a '.' in the symbol.
+ start = i;
+ while (i < length && name.CharAt(i) != '.') {
+ i++;
+ }
+ const intptr_t end = i;
+ if (end == length) {
+ break;
+ }
+
+ if (start != end) {
+ component = Symbols::New(thread, name, start, end - start);
+ component = obfuscator.Rename(component, /*atomic=*/true);
+ renamed.Add(component);
+ }
+
+ renamed.Add(Symbols::Dot());
+ i++; // Skip '.'
+ }
+
+ // Handle the last component [start, length).
+ // If symbol ends up at = and it is not one of '[]=', '==', '<=' or
+ // '>=' then we treat it as a setter symbol and follow the rule:
+ //
+ // Rename('ident=') = Rename('ident') '='
+ //
+ const bool is_setter = (length - start) > 1 &&
+ name.CharAt(length - 1) == '=' &&
+ !(name.Equals(Symbols::AssignIndexToken()) ||
+ name.Equals(Symbols::EqualOperator()) ||
+ name.Equals(Symbols::GreaterEqualOperator()) ||
+ name.Equals(Symbols::LessEqualOperator()));
+ const intptr_t end = length - (is_setter ? 1 : 0);
+
+ if ((start == 0) && (end == length) && name.IsSymbol()) {
+ component = name.raw();
+ } else {
+ component = Symbols::New(thread, name, start, end - start);
+ }
+ component = obfuscator.Rename(component, /*atomic=*/true);
+ renamed.Add(component);
+
+ if (is_setter) {
+ renamed.Add(Symbols::Equals());
+ }
+
+ name = Symbols::FromConcatAll(thread, renamed);
+ symbol.RawSetFieldAtOffset(kSymbolNameOffset, name);
+}
+
+void Obfuscator::Deobfuscate(Thread* thread,
+ const GrowableObjectArray& pieces) {
+ const Array& obfuscation_state = Array::Handle(
+ thread->zone(), thread->isolate()->object_store()->obfuscation_map());
+ if (obfuscation_state.IsNull()) {
+ return;
+ }
+
+ const Array& renames = Array::Handle(
+ thread->zone(), GetRenamesFromSavedState(obfuscation_state));
+
+ ObfuscationMap renames_map(renames.raw());
+ String& piece = String::Handle();
+ for (intptr_t i = 0; i < pieces.Length(); i++) {
+ piece ^= pieces.At(i);
+ ASSERT(piece.IsSymbol());
+
+ // Fast path: skip '.'
+ if (piece.raw() == Symbols::Dot().raw()) {
+ continue;
+ }
+
+ // Fast path: check if piece has an identity obfuscation.
+ if (renames_map.GetOrNull(piece) == piece.raw()) {
+ continue;
+ }
+
+ // Search through the whole obfuscation map until matching value is found.
+ // We are using linear search instead of generating a reverse mapping
+ // because we assume that Deobfuscate() method is almost never called.
+ ObfuscationMap::Iterator it(&renames_map);
+ while (it.MoveNext()) {
+ const intptr_t entry = it.Current();
+ if (renames_map.GetPayload(entry, 0) == piece.raw()) {
+ piece ^= renames_map.GetKey(entry);
+ pieces.SetAt(i, piece);
+ break;
+ }
+ }
+ }
+ renames_map.Release();
+}
+
+static const char* StringToCString(const String& str) {
+ const intptr_t len = Utf8::Length(str);
+ char* result = new char[len + 1];
+ str.ToUTF8(reinterpret_cast<uint8_t*>(result), len);
+ result[len] = 0;
+ return result;
+}
+
+const char** Obfuscator::SerializeMap(Thread* thread) {
+ const Array& obfuscation_state = Array::Handle(
+ thread->zone(), thread->isolate()->object_store()->obfuscation_map());
+ if (obfuscation_state.IsNull()) {
+ return NULL;
+ }
+
+ const Array& renames = Array::Handle(
+ thread->zone(), GetRenamesFromSavedState(obfuscation_state));
+ ObfuscationMap renames_map(renames.raw());
+
+ const char** result = new const char*[renames_map.NumOccupied() * 2 + 1];
+ intptr_t idx = 0;
+ String& str = String::Handle();
+
+ ObfuscationMap::Iterator it(&renames_map);
+ while (it.MoveNext()) {
+ const intptr_t entry = it.Current();
+ str ^= renames_map.GetKey(entry);
+ result[idx++] = StringToCString(str);
+ str ^= renames_map.GetPayload(entry, 0);
+ result[idx++] = StringToCString(str);
+ }
+ result[idx++] = NULL;
+ renames_map.Release();
+
+ return result;
+}
+
#endif // DART_PRECOMPILER
} // namespace dart
« no previous file with comments | « runtime/vm/precompiler.h ('k') | runtime/vm/raw_object.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698