8307526: [JFR] Better handling of tampered JFR repository
Reviewed-by: egahlin
This commit is contained in:
parent
0616648c59
commit
66d2736521
src
hotspot/share/jfr/jni
jdk.jfr/share/classes/jdk/jfr/internal
@ -389,3 +389,7 @@ JVM_ENTRY_NO_ENV(jlong, jfr_host_total_memory(JNIEnv* env, jclass jvm))
|
||||
return os::physical_memory();
|
||||
#endif
|
||||
JVM_END
|
||||
|
||||
JVM_ENTRY_NO_ENV(void, jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes))
|
||||
EventDataLoss::commit(bytes, min_jlong);
|
||||
JVM_END
|
||||
|
@ -156,6 +156,8 @@ jboolean JNICALL jfr_is_containerized(JNIEnv* env, jclass jvm);
|
||||
|
||||
jlong JNICALL jfr_host_total_memory(JNIEnv* env, jclass jvm);
|
||||
|
||||
void JNICALL jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@ -95,7 +95,8 @@ JfrJniMethodRegistration::JfrJniMethodRegistration(JNIEnv* env) {
|
||||
(char*)"isExcluded", (char*)"(Ljava/lang/Class;)Z", (void*)jfr_is_class_excluded,
|
||||
(char*)"isInstrumented", (char*)"(Ljava/lang/Class;)Z", (void*) jfr_is_class_instrumented,
|
||||
(char*)"isContainerized", (char*)"()Z", (void*) jfr_is_containerized,
|
||||
(char*)"hostTotalMemory", (char*)"()J", (void*) jfr_host_total_memory
|
||||
(char*)"hostTotalMemory", (char*)"()J", (void*) jfr_host_total_memory,
|
||||
(char*)"emitDataLoss", (char*)"(J)V", (void*)jfr_emit_data_loss
|
||||
};
|
||||
|
||||
const size_t method_array_length = sizeof(method) / sizeof(JNINativeMethod);
|
||||
|
@ -33,7 +33,7 @@ import java.util.SequencedSet;
|
||||
import jdk.jfr.internal.SecuritySupport.SafePath;
|
||||
|
||||
// This class keeps track of files that can't be deleted
|
||||
// so they can a later staged be removed.
|
||||
// so they can at a later staged be removed.
|
||||
final class FilePurger {
|
||||
|
||||
private static final SequencedSet<SafePath> paths = new LinkedHashSet<>();
|
||||
@ -62,6 +62,13 @@ final class FilePurger {
|
||||
}
|
||||
|
||||
private static boolean delete(SafePath p) {
|
||||
try {
|
||||
if (!SecuritySupport.exists(p)) {
|
||||
return true;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
try {
|
||||
SecuritySupport.delete(p);
|
||||
return true;
|
||||
|
@ -611,4 +611,11 @@ public final class JVM {
|
||||
* JVM runs in a container.
|
||||
*/
|
||||
public static native long hostTotalMemory();
|
||||
|
||||
/**
|
||||
* Emit a jdk.DataLoss event for the specified amount of bytes.
|
||||
*
|
||||
* @param bytes number of bytes that were lost
|
||||
*/
|
||||
public static native void emitDataLoss(long bytes);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
package jdk.jfr.internal;
|
||||
|
||||
import static jdk.jfr.internal.LogLevel.ERROR;
|
||||
import static jdk.jfr.internal.LogLevel.INFO;
|
||||
import static jdk.jfr.internal.LogLevel.TRACE;
|
||||
import static jdk.jfr.internal.LogLevel.WARN;
|
||||
@ -448,10 +449,19 @@ public final class PlatformRecorder {
|
||||
}
|
||||
|
||||
private void finishChunk(RepositoryChunk chunk, Instant time, PlatformRecording ignoreMe) {
|
||||
chunk.finish(time);
|
||||
for (PlatformRecording r : getRecordings()) {
|
||||
if (r != ignoreMe && r.getState() == RecordingState.RUNNING) {
|
||||
r.appendChunk(chunk);
|
||||
if (chunk.finish(time)) {
|
||||
for (PlatformRecording r : getRecordings()) {
|
||||
if (r != ignoreMe && r.getState() == RecordingState.RUNNING) {
|
||||
r.appendChunk(chunk);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (chunk.isMissingFile()) {
|
||||
// With one chunkfile found missing, its likely more could've been removed too. Iterate through all recordings,
|
||||
// and check for missing files. This will emit more error logs that can be seen in subsequent recordings.
|
||||
for (PlatformRecording r : getRecordings()) {
|
||||
r.removeNonExistantPaths();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Decrease initial reference count
|
||||
@ -498,17 +508,24 @@ public final class PlatformRecorder {
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
synchronized (this) {
|
||||
if (JVM.shouldRotateDisk()) {
|
||||
rotateDisk();
|
||||
}
|
||||
if (isToDisk()) {
|
||||
EventLog.update();
|
||||
long wait = Options.getWaitInterval();
|
||||
try {
|
||||
synchronized (this) {
|
||||
if (JVM.shouldRotateDisk()) {
|
||||
rotateDisk();
|
||||
}
|
||||
if (isToDisk()) {
|
||||
EventLog.update();
|
||||
}
|
||||
}
|
||||
long minDelta = PeriodicEvents.doPeriodic();
|
||||
wait = Math.min(minDelta, Options.getWaitInterval());
|
||||
} catch (Throwable t) {
|
||||
// Catch everything and log, but don't allow it to end the periodic task
|
||||
Logger.log(JFR_SYSTEM, ERROR, "Error in Periodic task: " + t.getClass().getName());
|
||||
} finally {
|
||||
takeNap(wait);
|
||||
}
|
||||
long minDelta = PeriodicEvents.doPeriodic();
|
||||
long wait = Math.min(minDelta, Options.getWaitInterval());
|
||||
takeNap(wait);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,12 +26,15 @@
|
||||
package jdk.jfr.internal;
|
||||
|
||||
import static jdk.jfr.internal.LogLevel.DEBUG;
|
||||
import static jdk.jfr.internal.LogLevel.ERROR;
|
||||
import static jdk.jfr.internal.LogLevel.INFO;
|
||||
import static jdk.jfr.internal.LogLevel.WARN;
|
||||
import static jdk.jfr.internal.LogTag.JFR;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.security.AccessControlContext;
|
||||
import java.security.AccessController;
|
||||
@ -718,17 +721,33 @@ public final class PlatformRecording implements AutoCloseable {
|
||||
|
||||
public void dumpStopped(WriteableUserPath userPath) throws IOException {
|
||||
synchronized (recorder) {
|
||||
userPath.doPrivilegedIO(() -> {
|
||||
try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(userPath.getReal(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
|
||||
long bytes = cc.transferTo(fc);
|
||||
Logger.log(LogTag.JFR, LogLevel.INFO, "Transferred " + bytes + " bytes from the disk repository");
|
||||
// No need to force if no data was transferred, which avoids IOException when device is /dev/null
|
||||
if (bytes != 0) {
|
||||
fc.force(true);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
transferChunksWithRetry(userPath);
|
||||
}
|
||||
}
|
||||
|
||||
private void transferChunksWithRetry(WriteableUserPath userPath) throws IOException {
|
||||
userPath.doPrivilegedIO(() -> {
|
||||
try {
|
||||
transferChunks(userPath);
|
||||
} catch (NoSuchFileException nsfe) {
|
||||
Logger.log(LogTag.JFR, LogLevel.ERROR, "Missing chunkfile when writing recording \"" + name + "\" (" + id + ") to " + userPath.getRealPathText() + ".");
|
||||
// if one chunkfile was missing, its likely more are missing
|
||||
removeNonExistantPaths();
|
||||
// and try the transfer again
|
||||
transferChunks(userPath);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void transferChunks(WriteableUserPath userPath) throws IOException {
|
||||
try (ChunksChannel cc = new ChunksChannel(chunks); FileChannel fc = FileChannel.open(userPath.getReal(), StandardOpenOption.WRITE, StandardOpenOption.APPEND)) {
|
||||
long bytes = cc.transferTo(fc);
|
||||
Logger.log(LogTag.JFR, LogLevel.INFO, "Transferred " + bytes + " bytes from the disk repository");
|
||||
// No need to force if no data was transferred, which avoids IOException when device is /dev/null
|
||||
if (bytes != 0) {
|
||||
fc.force(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -880,4 +899,27 @@ public final class PlatformRecording implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removeNonExistantPaths() {
|
||||
synchronized (recorder) {
|
||||
Iterator<RepositoryChunk> it = chunks.iterator();
|
||||
Logger.log(JFR, INFO, "Checking for missing chunkfiles for recording \"" + name + "\" (" + id + ")");
|
||||
while (it.hasNext()) {
|
||||
RepositoryChunk chunk = it.next();
|
||||
if (chunk.isMissingFile()) {
|
||||
String msg = "Chunkfile \"" + chunk.getFile() + "\" is missing. " +
|
||||
"Data loss might occur from " + chunk.getStartTime();
|
||||
if (chunk.getEndTime() != null) {
|
||||
msg += " to " + chunk.getEndTime();
|
||||
}
|
||||
Logger.log(JFR, ERROR, msg);
|
||||
|
||||
JVM.emitDataLoss(chunk.getSize());
|
||||
|
||||
it.remove();
|
||||
removed(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,10 @@ import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.time.Instant;
|
||||
import java.time.Period;
|
||||
import java.time.Duration;
|
||||
import java.util.Comparator;
|
||||
import java.util.Optional;
|
||||
|
||||
import jdk.jfr.internal.SecuritySupport.SafePath;
|
||||
|
||||
@ -55,20 +58,25 @@ public final class RepositoryChunk {
|
||||
this.unFinishedRAF = SecuritySupport.createRandomAccessFile(chunkFile);
|
||||
}
|
||||
|
||||
void finish(Instant endTime) {
|
||||
boolean finish(Instant endTime) {
|
||||
try {
|
||||
finishWithException(endTime);
|
||||
unFinishedRAF.close();
|
||||
size = SecuritySupport.getFileSize(chunkFile);
|
||||
this.endTime = endTime;
|
||||
if (Logger.shouldLog(LogTag.JFR_SYSTEM, LogLevel.DEBUG)) {
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, "Chunk finished: " + chunkFile);
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not finish chunk. " + e.getClass() + " "+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void finishWithException(Instant endTime) throws IOException {
|
||||
unFinishedRAF.close();
|
||||
this.size = SecuritySupport.getFileSize(chunkFile);
|
||||
this.endTime = endTime;
|
||||
if (Logger.shouldLog(LogTag.JFR_SYSTEM, LogLevel.DEBUG)) {
|
||||
Logger.log(LogTag.JFR_SYSTEM, LogLevel.DEBUG, "Chunk finished: " + chunkFile);
|
||||
final String reason;
|
||||
if (isMissingFile()) {
|
||||
reason = "Chunkfile \""+ getFile() + "\" is missing. " +
|
||||
"Data loss might occur from " + getStartTime() + " to " + endTime;
|
||||
} else {
|
||||
reason = e.getClass().getName();
|
||||
}
|
||||
Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not finish chunk. " + reason);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,16 +111,14 @@ public final class RepositoryChunk {
|
||||
}
|
||||
|
||||
private void destroy() {
|
||||
if (!isFinished()) {
|
||||
finish(Instant.MIN);
|
||||
}
|
||||
delete(chunkFile);
|
||||
try {
|
||||
unFinishedRAF.close();
|
||||
} catch (IOException e) {
|
||||
if (Logger.shouldLog(LogTag.JFR, LogLevel.ERROR)) {
|
||||
Logger.log(LogTag.JFR, LogLevel.ERROR, "Could not close random access file: " + chunkFile.toString() + ". File will not be deleted due to: " + e.getMessage());
|
||||
}
|
||||
} finally {
|
||||
delete(chunkFile);
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,4 +180,12 @@ public final class RepositoryChunk {
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isMissingFile() {
|
||||
try {
|
||||
return !SecuritySupport.exists(chunkFile);
|
||||
} catch (IOException ioe) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user