Skip to content

Commit

Permalink
JFR: Prevent chunk buffer overflows
Browse files Browse the repository at this point in the history
Detect chunk buffer overflow and prevent subsequent writes to the buffer.
Also, update chunk buffer size calculations.

Skip frames with incomplete data in stacktraces.

Add missing memory frees.

Signed-off-by: tajila <[email protected]>
  • Loading branch information
tajila committed Nov 4, 2024
1 parent 903b8ac commit 7388aed
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 91 deletions.
166 changes: 105 additions & 61 deletions runtime/vm/BufferWriter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ class VM_BufferWriter {
U_8 *_buffer;
U_8 *_cursor;
UDATA _size;

U_8 *_bufferEnd;
U_8 *_maxCursor;
bool _overflow;

#if defined(J9VM_ENV_LITTLE_ENDIAN)
static const bool _isLE = true;
Expand Down Expand Up @@ -94,8 +95,26 @@ class VM_BufferWriter {
: _buffer(buffer)
, _cursor(buffer)
, _size(size)
, _bufferEnd(buffer + size)
, _maxCursor(NULL)
, _overflow(false)
{
}

bool
checkBounds(UDATA size)
{
if (!_overflow && (_cursor + size >= _bufferEnd)) {
_overflow = true;
}

return !_overflow;
}

bool
overflowOccurred()
{
return _overflow;
}

U_64
Expand Down Expand Up @@ -123,50 +142,67 @@ class VM_BufferWriter {
}

void
writeU8(U_8 val)
writeU8NoCheck(U_8 val)
{
*_cursor = val;
_cursor += sizeof(U_8);
}

void
writeU8(U_8 val)
{
if (checkBounds(sizeof(U_8))) {
*_cursor = val;
_cursor += sizeof(U_8);
}
}

void
writeU16(U_16 val)
{
U_16 newVal = val;
if (_isLE) {
newVal = byteSwap(val);
if (checkBounds(sizeof(U_16))) {
U_16 newVal = val;
if (_isLE) {
newVal = byteSwap(val);
}
*(U_16 *)_cursor = newVal;
_cursor += sizeof(U_16);
}
*(U_16 *)_cursor = newVal;
_cursor += sizeof(U_16);
}

void
writeU32(U_32 val)
{
U_32 newVal = val;
if (_isLE) {
newVal = byteSwap(val);
if (checkBounds(sizeof(U_32))) {
U_32 newVal = val;
if (_isLE) {
newVal = byteSwap(val);
}
*(U_32 *)_cursor = newVal;
_cursor += sizeof(U_32);
}
*(U_32 *)_cursor = newVal;
_cursor += sizeof(U_32);
}

void
writeU64(U_64 val)
{
U_64 newVal = val;
if (_isLE) {
newVal = byteSwap(val);
if (checkBounds(sizeof(U_64))) {
U_64 newVal = val;
if (_isLE) {
newVal = byteSwap(val);
}
*(U_64 *)_cursor = newVal;
_cursor += sizeof(U_64);
}
*(U_64 *)_cursor = newVal;
_cursor += sizeof(U_64);
}

void
writeData(U_8 *data, UDATA size)
{
memcpy(_cursor, data, size);
_cursor += size;
if (checkBounds(size)) {
memcpy(_cursor, data, size);
_cursor += size;
}
}

U_8 *
Expand Down Expand Up @@ -203,19 +239,21 @@ class VM_BufferWriter {
void
writeLEB128(U_64 val)
{
U_64 newVal = val;
if (!_isLE) {
newVal = byteSwap(val);
}
do {
U_8 byte = newVal & 0x7F;
newVal >>= 7;

if (newVal > 0) {
byte |= 0x80;
if (checkBounds(9)) {
U_64 newVal = val;
if (!_isLE) {
newVal = byteSwap(val);
}
writeU8(byte);
} while (newVal > 0);
do {
U_8 byte = newVal & 0x7F;
newVal >>= 7;

if (newVal > 0) {
byte |= 0x80;
}
writeU8NoCheck(byte);
} while (newVal > 0);
}
}

void
Expand All @@ -230,19 +268,21 @@ class VM_BufferWriter {
void
writeLEB128PaddedU72(U_64 val)
{
U_64 newVal = val;
if (!_isLE) {
newVal = byteSwap(val);
if (checkBounds(9)) {
U_64 newVal = val;
if (!_isLE) {
newVal = byteSwap(val);
}
writeU8NoCheck((newVal & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 7) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 14) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 21) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 28) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 35) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 42) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 49) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 56) & 0x7F));
}
writeU8((newVal & 0x7F) | 0x80);
writeU8(((newVal >> 7) & 0x7F) | 0x80);
writeU8(((newVal >> 14) & 0x7F) | 0x80);
writeU8(((newVal >> 21) & 0x7F) | 0x80);
writeU8(((newVal >> 28) & 0x7F) | 0x80);
writeU8(((newVal >> 35) & 0x7F) | 0x80);
writeU8(((newVal >> 42) & 0x7F) | 0x80);
writeU8(((newVal >> 49) & 0x7F) | 0x80);
writeU8(((newVal >> 56) & 0x7F));
}

void
Expand All @@ -257,18 +297,20 @@ class VM_BufferWriter {
void
writeLEB128PaddedU64(U_64 val)
{
U_64 newVal = val;
if (!_isLE) {
newVal = byteSwap(val);
if (checkBounds(sizeof(U_64))) {
U_64 newVal = val;
if (!_isLE) {
newVal = byteSwap(val);
}
writeU8NoCheck((newVal & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 7) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 14) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 21) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 28) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 35) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 42) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 49) & 0x7F));
}
writeU8((newVal & 0x7F) | 0x80);
writeU8(((newVal >> 7) & 0x7F) | 0x80);
writeU8(((newVal >> 14) & 0x7F) | 0x80);
writeU8(((newVal >> 21) & 0x7F) | 0x80);
writeU8(((newVal >> 28) & 0x7F) | 0x80);
writeU8(((newVal >> 35) & 0x7F) | 0x80);
writeU8(((newVal >> 42) & 0x7F) | 0x80);
writeU8(((newVal >> 49) & 0x7F));
}

void
Expand All @@ -283,14 +325,16 @@ class VM_BufferWriter {
void
writeLEB128PaddedU32(U_32 val)
{
U_64 newVal = val;
if (!_isLE) {
newVal = byteSwap(val);
if (checkBounds(sizeof(U_32))) {
U_64 newVal = val;
if (!_isLE) {
newVal = byteSwap(val);
}
writeU8NoCheck((newVal & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 7) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 14) & 0x7F) | 0x80);
writeU8NoCheck(((newVal >> 21) & 0x7F));
}
writeU8((newVal & 0x7F) | 0x80);
writeU8(((newVal >> 7) & 0x7F) | 0x80);
writeU8(((newVal >> 14) & 0x7F) | 0x80);
writeU8(((newVal >> 21) & 0x7F));
}

static U_32
Expand Down
60 changes: 35 additions & 25 deletions runtime/vm/JFRChunkWriter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ class VM_JFRChunkWriter {
/* conservative sizing for JFR chunk */
static constexpr int STRING_HEADER_LENGTH = sizeof(U_64);
static constexpr int CHECKPOINT_EVENT_HEADER_AND_FOOTER = 68;
static constexpr int STRING_CONSTANT_SIZE = 128;
static constexpr int THREADSTATE_ENTRY_LENGTH = CHECKPOINT_EVENT_HEADER_AND_FOOTER + sizeof(threadStateNames) + (THREADSTATE_COUNT * STRING_HEADER_LENGTH);
static constexpr int CLASS_ENTRY_ENTRY_SIZE = (5 * sizeof(U_64)) + sizeof(U_8);
static constexpr int CLASSLOADER_ENTRY_SIZE = 3 * sizeof(U_64);
Expand All @@ -150,6 +151,7 @@ class VM_JFRChunkWriter {
static constexpr int THREAD_START_EVENT_SIZE = (6 * sizeof(U_64)) + sizeof(U_32);
static constexpr int THREAD_END_EVENT_SIZE = (4 * sizeof(U_64)) + sizeof(U_32);
static constexpr int THREAD_SLEEP_EVENT_SIZE = (7 * sizeof(U_64)) + sizeof(U_32);
static constexpr int MONITOR_WAIT_EVENT_SIZE = (9 * sizeof(U_64)) + sizeof(U_32);
static constexpr int JVM_INFORMATION_EVENT_SIZE = 3000;
static constexpr int PHYSICAL_MEMORY_EVENT_SIZE = (4 * sizeof(U_64)) + sizeof(U_32);
static constexpr int VIRTUALIZATION_INFORMATION_EVENT_SIZE = 50;
Expand Down Expand Up @@ -343,6 +345,10 @@ class VM_JFRChunkWriter {

writeJFRHeader();

if (_bufferWriter->overflowOccurred()) {
_buildResult = OutOfMemory;
}

if (isResultNotOKay()) {
Trc_VM_jfr_ErrorWritingChunk(_currentThread, _buildResult);
goto freeBuffer;
Expand Down Expand Up @@ -592,53 +598,57 @@ class VM_JFRChunkWriter {
UDATA
calculateRequiredBufferSize()
{
UDATA requireBufferSize = _constantPoolTypes.getRequiredBufferSize();
requireBufferSize += JFR_CHUNK_HEADER_SIZE;
UDATA requiredBufferSize = _constantPoolTypes.getRequiredBufferSize();
requiredBufferSize += JFR_CHUNK_HEADER_SIZE;

requiredBufferSize += METADATA_HEADER_SIZE;

requiredBufferSize += _vm->jfrState.metaDataBlobFileSize;

requireBufferSize += METADATA_HEADER_SIZE;
requiredBufferSize += THREADSTATE_ENTRY_LENGTH;

requireBufferSize += _vm->jfrState.metaDataBlobFileSize;
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getClassCount() * CLASS_ENTRY_ENTRY_SIZE);

requireBufferSize += THREADSTATE_ENTRY_LENGTH;
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getClassloaderCount() * CLASSLOADER_ENTRY_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getClassCount() * CLASS_ENTRY_ENTRY_SIZE);
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + ((_constantPoolTypes.getPackageCount() + _constantPoolTypes.getStringUTF8Count()) * STRING_CONSTANT_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getClassloaderCount() * CLASSLOADER_ENTRY_SIZE);
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getPackageCount() * PACKAGE_ENTRY_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getPackageCount() * PACKAGE_ENTRY_SIZE);
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getMethodCount() * METHOD_ENTRY_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getMethodCount() * METHOD_ENTRY_SIZE);
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getThreadCount() * THREAD_ENTRY_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getThreadCount() * THREAD_ENTRY_SIZE);
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getThreadGroupCount() * THREADGROUP_ENTRY_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getThreadGroupCount() * THREADGROUP_ENTRY_SIZE);
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getModuleCount() * MODULE_ENTRY_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getModuleCount() * MODULE_ENTRY_SIZE);
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getStackTraceCount() * STACKTRACE_ENTRY_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getStackTraceCount() * STACKTRACE_ENTRY_SIZE);
requiredBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getStackFrameCount() * STACKFRAME_ENTRY_SIZE);

requireBufferSize += CHECKPOINT_EVENT_HEADER_AND_FOOTER + (_constantPoolTypes.getStackFrameCount() * STACKFRAME_ENTRY_SIZE);
requiredBufferSize += _constantPoolTypes.getExecutionSampleCount() * EXECUTION_SAMPLE_EVENT_SIZE;

requireBufferSize += _constantPoolTypes.getExecutionSampleCount() * EXECUTION_SAMPLE_EVENT_SIZE;
requiredBufferSize += _constantPoolTypes.getThreadStartCount() * THREAD_START_EVENT_SIZE;

requireBufferSize += _constantPoolTypes.getThreadStartCount() * THREAD_START_EVENT_SIZE;
requiredBufferSize += _constantPoolTypes.getThreadEndCount() * THREAD_END_EVENT_SIZE;

requireBufferSize += _constantPoolTypes.getThreadEndCount() * THREAD_END_EVENT_SIZE;
requiredBufferSize += _constantPoolTypes.getThreadSleepCount() * THREAD_SLEEP_EVENT_SIZE;

requireBufferSize += _constantPoolTypes.getThreadSleepCount() * THREAD_SLEEP_EVENT_SIZE;
requiredBufferSize += _constantPoolTypes.getMonitorWaitCount() * MONITOR_WAIT_EVENT_SIZE;

requireBufferSize += JVM_INFORMATION_EVENT_SIZE;
requiredBufferSize += JVM_INFORMATION_EVENT_SIZE;

requireBufferSize += OS_INFORMATION_EVENT_SIZE;
requiredBufferSize += OS_INFORMATION_EVENT_SIZE;

requireBufferSize += PHYSICAL_MEMORY_EVENT_SIZE;
requiredBufferSize += PHYSICAL_MEMORY_EVENT_SIZE;

requireBufferSize += VIRTUALIZATION_INFORMATION_EVENT_SIZE;
requiredBufferSize += VIRTUALIZATION_INFORMATION_EVENT_SIZE;

requireBufferSize += CPU_INFORMATION_EVENT_SIZE;
requiredBufferSize += CPU_INFORMATION_EVENT_SIZE;

requireBufferSize += INITIAL_SYSTEM_PROPERTY_EVENT_SIZE;
return requireBufferSize;
requiredBufferSize += INITIAL_SYSTEM_PROPERTY_EVENT_SIZE;
return requiredBufferSize;
}

~VM_JFRChunkWriter()
Expand Down
26 changes: 26 additions & 0 deletions runtime/vm/JFRConstantPoolTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1152,5 +1152,31 @@ VM_JFRConstantPoolTypes::freeStackStraceEntries(void *entry, void *userData)
return FALSE;
}

UDATA
VM_JFRConstantPoolTypes::freeThreadNameEntries(void *entry, void *userData)
{
ThreadEntry *tableEntry = (ThreadEntry *) entry;
J9VMThread *currentThread = (J9VMThread *)userData;
PORT_ACCESS_FROM_VMC(currentThread);

j9mem_free_memory(tableEntry->javaThreadName);
tableEntry->javaThreadName = NULL;

return FALSE;
}

UDATA
VM_JFRConstantPoolTypes::freeThreadGroupNameEntries(void *entry, void *userData)
{
ThreadGroupEntry *tableEntry = (ThreadGroupEntry *) entry;
J9VMThread *currentThread = (J9VMThread *)userData;
PORT_ACCESS_FROM_VMC(currentThread);

j9mem_free_memory(tableEntry->name);
tableEntry->name = NULL;

return FALSE;
}


#endif /* defined(J9VM_OPT_JFR) */
Loading

0 comments on commit 7388aed

Please sign in to comment.