8249627: Degrade Thread.suspend and Thread.resume

Reviewed-by: cjplummer, sspitsyn, dholmes, jpai
This commit is contained in:
Alan Bateman 2022-09-27 11:43:59 +00:00
parent bc12e9553d
commit 1abf971b93
15 changed files with 113 additions and 232 deletions

View File

@ -194,7 +194,6 @@ JVM_RegisterLambdaProxyClassForArchiving
JVM_RegisterSignal
JVM_ReleaseUTF
JVM_ReportFinalizationComplete
JVM_ResumeThread
JVM_ExtentLocalCache
JVM_SetExtentLocalCache
JVM_SetArrayElement
@ -207,7 +206,6 @@ JVM_Sleep
JVM_StartThread
JVM_StopThread
JVM_SupportsCX8
JVM_SuspendThread
JVM_TotalMemory
JVM_UnloadLibrary
JVM_WaitForReferencePendingList

View File

@ -272,12 +272,6 @@ JVM_StopThread(JNIEnv *env, jobject thread, jobject exception);
JNIEXPORT jboolean JNICALL
JVM_IsThreadAlive(JNIEnv *env, jobject thread);
JNIEXPORT void JNICALL
JVM_SuspendThread(JNIEnv *env, jobject thread);
JNIEXPORT void JNICALL
JVM_ResumeThread(JNIEnv *env, jobject thread);
JNIEXPORT void JNICALL
JVM_SetThreadPriority(JNIEnv *env, jobject thread, jint prio);

View File

@ -3026,29 +3026,6 @@ JVM_ENTRY(jboolean, JVM_IsThreadAlive(JNIEnv* env, jobject jthread))
JVM_END
JVM_ENTRY(void, JVM_SuspendThread(JNIEnv* env, jobject jthread))
ThreadsListHandle tlh(thread);
JavaThread* receiver = NULL;
bool is_alive = tlh.cv_internal_thread_to_JavaThread(jthread, &receiver, NULL);
if (is_alive) {
// jthread refers to a live JavaThread, but java_suspend() will
// detect a thread that has started to exit and will ignore it.
receiver->java_suspend();
}
JVM_END
JVM_ENTRY(void, JVM_ResumeThread(JNIEnv* env, jobject jthread))
ThreadsListHandle tlh(thread);
JavaThread* receiver = NULL;
bool is_alive = tlh.cv_internal_thread_to_JavaThread(jthread, &receiver, NULL);
if (is_alive) {
// jthread refers to a live JavaThread.
receiver->java_resume();
}
JVM_END
JVM_ENTRY(void, JVM_SetThreadPriority(JNIEnv* env, jobject jthread, jint prio))
ThreadsListHandle tlh(thread);
oop java_thread = NULL;

View File

@ -1263,12 +1263,10 @@ jvmtiEnv *jvmti;
have this state flag set instead of <code>JVMTI_THREAD_STATE_SLEEPING</code>.
</constant>
<constant id="JVMTI_THREAD_STATE_SUSPENDED" num="0x100000">
Thread suspended.
<code>java.lang.Thread.suspend()</code>
or a <jvmti/> suspend function
(such as <functionlink id="SuspendThread"></functionlink>)
has been called on the thread. If this bit
is set, the other bits refer to the thread state before suspension.
Thread is suspended by a suspend function
(such as <functionlink id="SuspendThread"></functionlink>).
If this bit is set, the other bits refer to the thread state before
suspension.
</constant>
<constant id="JVMTI_THREAD_STATE_INTERRUPTED" num="0x200000">
Thread has been interrupted.
@ -1780,7 +1778,6 @@ jvmtiEnv *jvmti;
Any threads currently suspended through
a <jvmti/> suspend function (eg.
<functionlink id="SuspendThread"></functionlink>)
or <code>java.lang.Thread.suspend()</code>
will resume execution;
all other threads are unaffected.
</description>
@ -1815,7 +1812,6 @@ jvmtiEnv *jvmti;
Any thread suspended through
a <jvmti/> suspend function (eg.
<functionlink id="SuspendThreadList"></functionlink>)
or <code>java.lang.Thread.suspend()</code>
will resume execution.
</description>
<origin>jvmdi</origin>

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1994, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1994, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -27,12 +27,9 @@ package java.lang;
/**
* Thrown to indicate that a thread is not in an appropriate state
* for the requested operation. See, for example, the
* {@code suspend} and {@code resume} methods in class
* {@code Thread}.
* for the requested operation.
*
* @see java.lang.Thread#resume()
* @see java.lang.Thread#suspend()
* @see Thread#start()
* @since 1.0
*/
public class IllegalThreadStateException extends IllegalArgumentException {

View File

@ -171,9 +171,8 @@ import java.lang.module.ModuleFinder;
* <tr>
* <th scope="row">modifyThread</th>
* <td>Modification of threads, e.g., via calls to Thread
* {@code interrupt, stop, suspend, resume, setDaemon, setPriority,
* setName} and {@code setUncaughtExceptionHandler}
* methods</td>
* {@code interrupt, setDaemon, setPriority, setName} and
* {@code setUncaughtExceptionHandler} methods</td>
* <td>This allows an attacker to modify the behaviour of
* any thread in the system.</td>
* </tr>
@ -181,9 +180,7 @@ import java.lang.module.ModuleFinder;
* <tr>
* <th scope="row">modifyThreadGroup</th>
* <td>modification of thread groups, e.g., via calls to ThreadGroup
* {@code destroy}, {@code getParent}, {@code resume},
* {@code setDaemon}, {@code setMaxPriority}, {@code stop},
* and {@code suspend} methods</td>
* {@code getParent}, {@code setDaemon}, and {@code setMaxPriority} methods</td>
* <td>This allows an attacker to create thread groups and
* set their run priority.</td>
* </tr>

View File

@ -494,7 +494,6 @@ public class SecurityManager {
* calling thread is not allowed to modify the thread argument.
* <p>
* This method is invoked for the current security manager by the
* {@code stop}, {@code suspend}, {@code resume},
* {@code setPriority}, {@code setName}, and
* {@code setDaemon} methods of class {@code Thread}.
* <p>
@ -523,12 +522,9 @@ public class SecurityManager {
* permission to modify the thread.
* @throws NullPointerException if the thread argument is
* {@code null}.
* @see java.lang.Thread#resume() resume
* @see java.lang.Thread#setDaemon(boolean) setDaemon
* @see java.lang.Thread#setName(java.lang.String) setName
* @see java.lang.Thread#setPriority(int) setPriority
* @see java.lang.Thread#stop() stop
* @see java.lang.Thread#suspend() suspend
* @see #checkPermission(java.security.Permission) checkPermission
*/
public void checkAccess(Thread t) {
@ -547,9 +543,8 @@ public class SecurityManager {
* <p>
* This method is invoked for the current security manager when a
* new child thread or child thread group is created, and by the
* {@code setDaemon}, {@code setMaxPriority},
* {@code stop}, {@code suspend}, {@code resume}, and
* {@code destroy} methods of class {@code ThreadGroup}.
* {@code setDaemon} and {@code setMaxPriority} methods of class
* {@code ThreadGroup}.
* <p>
* If the thread group argument is the system thread group (
* has a {@code null} parent) then
@ -576,12 +571,8 @@ public class SecurityManager {
* permission to modify the thread group.
* @throws NullPointerException if the thread group argument is
* {@code null}.
* @see java.lang.ThreadGroup#destroy() destroy
* @see java.lang.ThreadGroup#resume() resume
* @see java.lang.ThreadGroup#setDaemon(boolean) setDaemon
* @see java.lang.ThreadGroup#setMaxPriority(int) setMaxPriority
* @see java.lang.ThreadGroup#stop() stop
* @see java.lang.ThreadGroup#suspend() suspend
* @see #checkPermission(java.security.Permission) checkPermission
*/
public void checkAccess(ThreadGroup g) {

View File

@ -1798,65 +1798,41 @@ public class Thread implements Runnable {
private native boolean isAlive0();
/**
* Suspends this thread.
* <p>
* First, the {@code checkAccess} method of this thread is called
* with no arguments. This may result in throwing a
* {@code SecurityException} (in the current thread).
* <p>
* If the thread is alive, it is suspended and makes no further
* progress unless and until it is resumed.
* Throws {@code UnsupportedOperationException}.
*
* @throws SecurityException if the current thread cannot modify
* this thread.
* @throws UnsupportedOperationException if invoked on a virtual thread
* @see #checkAccess
* @deprecated This method has been deprecated, as it is
* inherently deadlock-prone. If the target thread holds a lock on the
* monitor protecting a critical system resource when it is suspended, no
* thread can access this resource until the target thread is resumed. If
* the thread that would resume the target thread attempts to lock this
* monitor prior to calling {@code resume}, deadlock results. Such
* deadlocks typically manifest themselves as "frozen" processes.
* For more information, see
* <a href="{@docRoot}/java.base/java/lang/doc-files/threadPrimitiveDeprecation.html">Why
* are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
* @throws UnsupportedOperationException always
*
* @deprecated This method was originally specified to suspend a thread.
* It was inherently deadlock-prone. If the target thread held a lock on
* a monitor protecting a critical system resource when it was suspended,
* no thread could access the resource until the target thread was resumed.
* If the thread intending to resume the target thread attempted to lock
* the monitor prior to calling {@code resume}, deadlock would result.
* Such deadlocks typically manifested themselves as "frozen" processes.
* For more information, see
* <a href="{@docRoot}/java.base/java/lang/doc-files/threadPrimitiveDeprecation.html">Why
* are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
*/
@Deprecated(since="1.2", forRemoval=true)
public final void suspend() {
checkAccess();
if (isVirtual())
throw new UnsupportedOperationException();
suspend0();
throw new UnsupportedOperationException();
}
/**
* Resumes a suspended thread.
* <p>
* First, the {@code checkAccess} method of this thread is called
* with no arguments. This may result in throwing a
* {@code SecurityException} (in the current thread).
* <p>
* If the thread is alive but suspended, it is resumed and is
* permitted to make progress in its execution.
* Throws {@code UnsupportedOperationException}.
*
* @throws SecurityException if the current thread cannot modify this
* thread.
* @throws UnsupportedOperationException if invoked on a virtual thread
* @see #checkAccess
* @see #suspend()
* @deprecated This method exists solely for use with {@link #suspend},
* which has been deprecated because it is deadlock-prone.
* @throws UnsupportedOperationException always
*
* @deprecated This method was originally specified to resume a thread
* suspended with {@link #suspend()}. Suspending a thread was
* inherently deadlock-prone.
* For more information, see
* <a href="{@docRoot}/java.base/java/lang/doc-files/threadPrimitiveDeprecation.html">Why
* are Thread.stop, Thread.suspend and Thread.resume Deprecated?</a>.
*/
@Deprecated(since="1.2", forRemoval=true)
public final void resume() {
checkAccess();
if (isVirtual())
throw new UnsupportedOperationException();
resume0();
throw new UnsupportedOperationException();
}
/**
@ -3035,8 +3011,6 @@ public class Thread implements Runnable {
/* Some private helper methods */
private native void setPriority0(int newPriority);
private native void suspend0();
private native void resume0();
private native void interrupt0();
private static native void clearInterruptEvent();
private native void setNativeName(String name);

View File

@ -160,13 +160,14 @@ operations for which thread.stop and thread.interrupt do not work
properly.</p>
<hr>
<h2>Why are <code>Thread.suspend</code> and
<code>Thread.resume</code> deprecated?</h2>
<p><code>Thread.suspend</code> is inherently deadlock-prone. If the
target thread holds a lock on the monitor protecting a critical
system resource when it is suspended, no thread can access this
resource until the target thread is resumed. If the thread that
would resume the target thread attempts to lock this monitor prior
to calling <code>resume</code>, deadlock results. Such deadlocks
<code>Thread.resume</code> deprecated and the ability to suspend or
resume a thread removed?</h2>
<p><code>Thread.suspend</code> was inherently deadlock-prone. If the
target thread held a lock on a monitor protecting a critical
system resource when it is suspended, no thread could access the
resource until the target thread was resumed. If the thread intending
to resume the target thread attempted to lock the monitor prior
to calling <code>resume</code>, deadlock resulted. Such deadlocks
typically manifest themselves as "frozen" processes.</p>
<hr>
<h2>What should I use instead of <code>Thread.suspend</code> and

View File

@ -38,8 +38,6 @@
static JNINativeMethod methods[] = {
{"start0", "()V", (void *)&JVM_StartThread},
{"isAlive0", "()Z", (void *)&JVM_IsThreadAlive},
{"suspend0", "()V", (void *)&JVM_SuspendThread},
{"resume0", "()V", (void *)&JVM_ResumeThread},
{"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority},
{"yield0", "()V", (void *)&JVM_Yield},
{"sleep0", "(J)V", (void *)&JVM_Sleep},

View File

@ -182,8 +182,7 @@ JDWP "Java(tm) Debug Wire Protocol"
"Suspends the execution of the application running in the target "
"VM. All Java threads currently running will be suspended. "
"<p>"
"Unlike java.lang.Thread.suspend, "
"suspends of both the virtual machine and individual threads are "
"Suspends of both the virtual machine and individual threads are "
"counted. Before a thread will run again, it must be resumed through "
"the <a href=\"#JDWP_VirtualMachine_Resume\">VM-level resume</a> command "
"or the <a href=\"#JDWP_ThreadReference_Resume\">thread-level resume</a> command "
@ -1835,21 +1834,17 @@ JDWP "Java(tm) Debug Wire Protocol"
(Command Suspend=2
"Suspends the thread. "
"<p>"
"Unlike java.lang.Thread.suspend(), suspends of both "
"the virtual machine and individual threads are counted. Before "
"a thread will run again, it must be resumed the same number "
"of times it has been suspended. "
"Suspends of both the virtual machine and individual threads are "
"counted. Before a thread will run again, it must be resumed the "
"same number of times it has been suspended. "
"<p>"
"Suspending single threads with command has the same "
"dangers java.lang.Thread.suspend(). If the suspended "
"thread holds a monitor needed by another running thread, "
"Suspending single threads is inherently deadlock-prone. If the "
"suspended thread holds a monitor needed by another running thread, "
"deadlock is possible in the target VM (at least until the "
"suspended thread is resumed again). "
"<p>"
"The suspended thread is guaranteed to remain suspended until "
"resumed through one of the JDI resume methods mentioned above; "
"the application in the target VM cannot resume the suspended thread "
"through {@link java.lang.Thread#resume}. "
"resumed through one of the JDI resume methods mentioned above. "
"<p>"
"Note that this doesn't change the status of the thread (see the "
"<a href=\"#JDWP_ThreadReference_Status\">ThreadStatus</a> command.) "

View File

@ -71,23 +71,21 @@ public interface ThreadReference extends ObjectReference {
* {@link #resume} or resumed with other threads through
* {@link VirtualMachine#resume}.
* <p>
* Unlike {@link java.lang.Thread#suspend},
* suspends of both the virtual machine and individual threads are
* Suspends of both the virtual machine and individual threads are
* counted. Before a thread will run again, it must be resumed
* (through {@link #resume} or {@link ThreadReference#resume})
* (through {@link #resume} or {@link VirtualMachine#resume})
* the same number of times it has been suspended.
* <p>
* Suspending single threads with this method has the same dangers
* as {@link java.lang.Thread#suspend()}. If the suspended thread
* holds a monitor needed by another running thread, deadlock is
* possible in the target VM (at least until the suspended thread
* Suspending single threads with this method is inherently deadlock-prone.
* If the suspended thread holds a monitor needed by another running thread,
* deadlock is possible in the target VM (at least until the suspended thread
* is resumed again).
* <p>
* The suspended thread is guaranteed to remain suspended until
* resumed through one of the JDI resume methods mentioned above;
* the application in the target VM cannot resume the suspended thread
* through {@link java.lang.Thread#resume}.
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only - see {@link VirtualMachine#canBeModified()}.
* resumed through one of the JDI resume methods mentioned above.
*
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only
* @see VirtualMachine#canBeModified()
*/
@SuppressWarnings("javadoc")
void suspend();
@ -101,7 +99,9 @@ public interface ThreadReference extends ObjectReference {
* the thread will continue to execute.
* Note: the normal way to resume from an event related suspension is
* via {@link EventSet#resume}.
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only - see {@link VirtualMachine#canBeModified()}.
*
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only
* @see VirtualMachine#canBeModified()
*/
void resume();

View File

@ -277,13 +277,13 @@ public interface VirtualMachine extends Mirror {
* Suspends the execution of the application running in this
* virtual machine. All threads currently running will be suspended.
* <p>
* Unlike {@link java.lang.Thread#suspend Thread.suspend()},
* suspends of both the virtual machine and individual threads are
* Suspends of both the virtual machine and individual threads are
* counted. Before a thread will run again, it must be resumed
* (through {@link #resume} or {@link ThreadReference#resume})
* the same number of times it has been suspended.
*
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only - see {@link VirtualMachine#canBeModified()}.
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only
* @see #canBeModified()
*/
void suspend();
@ -292,9 +292,9 @@ public interface VirtualMachine extends Mirror {
* virtual machine. All threads are resumed as documented in
* {@link ThreadReference#resume}.
*
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only - see {@link VirtualMachine#canBeModified()}.
*
* @see #suspend
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only
* @see #suspend()
* @see #canBeModified()
*/
void resume();

View File

@ -1,56 +0,0 @@
/*
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @bug 8205132
* @summary Test Thread.countStackFrames()
* @run testng CountStackFrames
*/
import org.testng.annotations.Test;
public class CountStackFrames {
// current thread
@Test(expectedExceptions = UnsupportedOperationException.class)
public void testCurrentThread() {
Thread.currentThread().countStackFrames();
}
// unstarted thread
@Test(expectedExceptions = UnsupportedOperationException.class)
public void testUnstartedThread() {
Thread thread = new Thread(() -> { });
thread.countStackFrames();
}
// terminated thread
@Test(expectedExceptions = UnsupportedOperationException.class)
public void testTerminatedThread() throws Exception {
Thread thread = new Thread(() -> { });
thread.start();
thread.join();
thread.countStackFrames();
}
}

View File

@ -22,43 +22,61 @@
*/
/* @test
* @bug 8289610
* @summary Test Thread.stop throws UnsupportedOperationException
* @run testng StopTest
* @bug 8289610 8249627 8205132
* @summary Test that Thread stop/suspend/resume/countStackFrames throw UOE
* @run junit DegradedMethodsThrowUOE
*/
import java.time.Duration;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.LockSupport;
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.junit.jupiter.api.Assertions.*;
public class StopTest {
class DegradedMethodsThrowUOE {
/**
* Test stop on the current thread.
* Returns a stream of operations on a Thread that should throw UOE.
*/
@Test
public void testCurrentThread() {
var thread = Thread.currentThread();
assertThrows(UnsupportedOperationException.class, thread::stop);
static Stream<Consumer<Thread>> ops() {
return Stream.<Consumer<Thread>>of(
Thread::stop,
Thread::suspend,
Thread::resume,
Thread::countStackFrames
);
}
/**
* Test stop on an unstarted thread.
* Test degraded method on current thread.
*/
@Test
public void testUnstartedThread() {
@ParameterizedTest
@MethodSource("ops")
void testCurrentThread(Consumer<Thread> op) {
var thread = Thread.currentThread();
assertThrows(UnsupportedOperationException.class, () -> op.accept(thread));
}
/**
* Test degraded method on an unstarted thread.
*/
@ParameterizedTest
@MethodSource("ops")
void testUnstartedThread(Consumer<Thread> op) {
Thread thread = new Thread(() -> { });
assertThrows(UnsupportedOperationException.class, thread::stop);
assertThrows(UnsupportedOperationException.class, () -> op.accept(thread));
assertTrue(thread.getState() == Thread.State.NEW);
}
/**
* Test stop on a thread spinning in a loop.
* Test degraded method on a thread spinning in a loop.
*/
@Test
public void testRunnableThread() throws Exception {
@ParameterizedTest
@MethodSource("ops")
void testRunnableThread(Consumer<Thread> op) throws Exception {
AtomicBoolean done = new AtomicBoolean();
Thread thread = new Thread(() -> {
while (!done.get()) {
@ -67,7 +85,7 @@ public class StopTest {
});
thread.start();
try {
assertThrows(UnsupportedOperationException.class, thread::stop);
assertThrows(UnsupportedOperationException.class, () -> op.accept(thread));
// thread should not terminate
boolean terminated = thread.join(Duration.ofMillis(500));
@ -79,10 +97,11 @@ public class StopTest {
}
/**
* Test stop on a thread that is parked.
* Test degraded method on a thread that is parked.
*/
@Test
public void testWaitingThread() throws Exception {
@ParameterizedTest
@MethodSource("ops")
void testWaitingThread(Consumer<Thread> op) throws Exception {
Thread thread = new Thread(LockSupport::park);
thread.start();
try {
@ -90,7 +109,7 @@ public class StopTest {
while ((thread.getState() != Thread.State.WAITING)) {
Thread.sleep(10);
}
assertThrows(UnsupportedOperationException.class, thread::stop);
assertThrows(UnsupportedOperationException.class, () -> op.accept(thread));
assertTrue(thread.getState() == Thread.State.WAITING);
} finally {
LockSupport.unpark(thread);
@ -99,15 +118,15 @@ public class StopTest {
}
/**
* Test stop on a terminated thread.
* Test degraded method on a terminated thread.
*/
@Test
public void testTerminatedThread() throws Exception {
@ParameterizedTest
@MethodSource("ops")
void testTerminatedThread(Consumer<Thread> op) throws Exception {
Thread thread = new Thread(() -> { });
thread.start();
thread.join();
assertThrows(UnsupportedOperationException.class, thread::stop);
assertThrows(UnsupportedOperationException.class, () -> op.accept(thread));
assertTrue(thread.getState() == Thread.State.TERMINATED);
}
}