Avoid using SafeCopy on userfaultfd compacted spaces
Userfaultfd doesn't allow faults generating from kernel-space for
unprivileged processes. Therefore, avoid using SafeCopy to fetch
class in fault-handler.
Also add a run-test to cause null-pointer exceptions which exercises
fault-handler.
Bug: 160737021
Test: ART_USE_READ_BARRIER=false art/test/testrunner/testrunner.py -t 2045-uffd-kernelfault
Change-Id: If54bb01d441fab5489289e0ec195896700fac662
(cherry picked from commit f716e21fb87e4fd1c875997cf11882352b9fbab5)
Merged-In: If54bb01d441fab5489289e0ec195896700fac662
diff --git a/runtime/base/gc_visited_arena_pool.h b/runtime/base/gc_visited_arena_pool.h
index 57b742d..4f176ef 100644
--- a/runtime/base/gc_visited_arena_pool.h
+++ b/runtime/base/gc_visited_arena_pool.h
@@ -108,6 +108,16 @@
void LockReclaimMemory() override {}
void TrimMaps() override {}
+ bool Contains(void* ptr) {
+ std::lock_guard<std::mutex> lock(lock_);
+ for (auto& map : maps_) {
+ if (map.HasAddress(ptr)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
template <typename PageVisitor>
void VisitRoots(PageVisitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
std::lock_guard<std::mutex> lock(lock_);
diff --git a/runtime/fault_handler.cc b/runtime/fault_handler.cc
index f8bd213..c6940fa 100644
--- a/runtime/fault_handler.cc
+++ b/runtime/fault_handler.cc
@@ -25,6 +25,7 @@
#include "base/safe_copy.h"
#include "base/stl_util.h"
#include "dex/dex_file_types.h"
+#include "gc/space/bump_pointer_space.h"
#include "jit/jit.h"
#include "jit/jit_code_cache.h"
#include "mirror/class.h"
@@ -62,9 +63,20 @@
static mirror::Class* SafeGetDeclaringClass(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (gUseUserfaultfd) {
+ // Avoid SafeCopy on userfaultfd updated memory ranges as kernel-space
+ // userfaults are not allowed, which can otherwise happen if compaction is
+ // simultaneously going on.
+ Runtime* runtime = Runtime::Current();
+ DCHECK_NE(runtime->GetHeap()->MarkCompactCollector(), nullptr);
+ GcVisitedArenaPool* pool = static_cast<GcVisitedArenaPool*>(runtime->GetLinearAllocArenaPool());
+ if (pool->Contains(method)) {
+ return method->GetDeclaringClassUnchecked<kWithoutReadBarrier>().Ptr();
+ }
+ }
+
char* method_declaring_class =
reinterpret_cast<char*>(method) + ArtMethod::DeclaringClassOffset().SizeValue();
-
// ArtMethod::declaring_class_ is a GcRoot<mirror::Class>.
// Read it out into as a CompressedReference directly for simplicity's sake.
mirror::CompressedReference<mirror::Class> cls;
@@ -84,8 +96,18 @@
}
static mirror::Class* SafeGetClass(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_) {
- char* obj_cls = reinterpret_cast<char*>(obj) + mirror::Object::ClassOffset().SizeValue();
+ if (gUseUserfaultfd) {
+ // Avoid SafeCopy on userfaultfd updated memory ranges as kernel-space
+ // userfaults are not allowed, which can otherwise happen if compaction is
+ // simultaneously going on.
+ gc::Heap* heap = Runtime::Current()->GetHeap();
+ DCHECK_NE(heap->MarkCompactCollector(), nullptr);
+ if (heap->GetBumpPointerSpace()->Contains(obj)) {
+ return obj->GetClass();
+ }
+ }
+ char* obj_cls = reinterpret_cast<char*>(obj) + mirror::Object::ClassOffset().SizeValue();
mirror::HeapReference<mirror::Class> cls;
ssize_t rc = SafeCopy(&cls, obj_cls, sizeof(cls));
CHECK_NE(-1, rc);
diff --git a/test/2045-uffd-kernelfault/expected-stderr.txt b/test/2045-uffd-kernelfault/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2045-uffd-kernelfault/expected-stderr.txt
diff --git a/test/2045-uffd-kernelfault/expected-stdout.txt b/test/2045-uffd-kernelfault/expected-stdout.txt
new file mode 100644
index 0000000..a965a70
--- /dev/null
+++ b/test/2045-uffd-kernelfault/expected-stdout.txt
@@ -0,0 +1 @@
+Done
diff --git a/test/2045-uffd-kernelfault/info.txt b/test/2045-uffd-kernelfault/info.txt
new file mode 100644
index 0000000..c0967d5
--- /dev/null
+++ b/test/2045-uffd-kernelfault/info.txt
@@ -0,0 +1,2 @@
+Test that fault-handler doesn't cause userfaultfd kernel-faults, which are not
+allowed in unpriviledged processes.
diff --git a/test/2045-uffd-kernelfault/run.py b/test/2045-uffd-kernelfault/run.py
new file mode 100644
index 0000000..5b262bb
--- /dev/null
+++ b/test/2045-uffd-kernelfault/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+def run(ctx, args):
+ # Limit the Java heap to 20MiB to force more GCs.
+ ctx.default_run(args, runtime_option=["-Xmx20m"])
diff --git a/test/2045-uffd-kernelfault/src/Main.java b/test/2045-uffd-kernelfault/src/Main.java
new file mode 100644
index 0000000..c5fac30
--- /dev/null
+++ b/test/2045-uffd-kernelfault/src/Main.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+ // TODO: Reduce it once the userfaultfd GC is tested long enough.
+ static final long DURATION_IN_MILLIS = 10_000;
+
+ static public Object obj = null;
+ static public Object[] array = new Object[4096];
+
+ public static void main(String args[]) {
+ final long start_time = System.currentTimeMillis();
+ long end_time = start_time;
+ int idx = 0;
+ while (end_time - start_time < DURATION_IN_MILLIS) {
+ try {
+ // Trigger a null-pointer exception
+ System.out.println(obj.toString());
+ } catch (NullPointerException npe) {
+ // Small enough to be not allocated in large-object space and hence keep the compaction
+ // phase longer, while keeping marking phase shorter (as there aren't any references to
+ // chase).
+ array[idx++] = new byte[3000];
+ idx %= array.length;
+ }
+ end_time = System.currentTimeMillis();
+ }
+ System.out.println("Done");
+ }
+}