mirror of
https://github.com/NohamR/Reclass.git
synced 2026-05-10 19:59:21 +00:00
Fix MCP use-after-free, scanner chunk overlap, build scripts
- MCP bridge: guard against use-after-free when client disconnects during sendJson flush by re-checking m_client after write - Scanner engine: fix chunk overlap advancing past region end on final chunk; fix fallback region flags for providers without enumerateRegions - Build scripts: prefer GCC MinGW over LLVM-MinGW in PATH detection
This commit is contained in:
@@ -283,9 +283,10 @@ function Find-MinGWDirectory {
|
|||||||
$toolsDir = Join-Path $qtRoot "Tools"
|
$toolsDir = Join-Path $qtRoot "Tools"
|
||||||
|
|
||||||
if (Test-Path $toolsDir) {
|
if (Test-Path $toolsDir) {
|
||||||
|
# Prefer GCC-based MinGW (has g++.exe); exclude llvm-mingw. Prefer 64-bit, then newest.
|
||||||
$mingwToolDirs = Get-ChildItem -Path $toolsDir -Directory -ErrorAction SilentlyContinue | Where-Object {
|
$mingwToolDirs = Get-ChildItem -Path $toolsDir -Directory -ErrorAction SilentlyContinue | Where-Object {
|
||||||
$_.Name -match 'mingw'
|
$_.Name -match '^mingw\d+_\d+$'
|
||||||
}
|
} | Sort-Object -Property @{ Expression = { if ($_.Name -match '_64$') { 1 } else { 0 } }; Descending = $true }, Name -Descending
|
||||||
|
|
||||||
foreach ($dir in $mingwToolDirs) {
|
foreach ($dir in $mingwToolDirs) {
|
||||||
$testBin = Join-Path $dir.FullName "bin\g++.exe"
|
$testBin = Join-Path $dir.FullName "bin\g++.exe"
|
||||||
|
|||||||
@@ -318,10 +318,10 @@ $qtRoot = Split-Path (Split-Path $selectedQtDir -Parent) -Parent
|
|||||||
$toolsDir = Join-Path $qtRoot "Tools"
|
$toolsDir = Join-Path $qtRoot "Tools"
|
||||||
|
|
||||||
if (Test-Path $toolsDir) {
|
if (Test-Path $toolsDir) {
|
||||||
# Look for MinGW tools directory
|
# Prefer GCC-based MinGW (has g++.exe); exclude llvm-mingw. Prefer 64-bit, then newest.
|
||||||
$mingwToolDirs = Get-ChildItem -Path $toolsDir -Directory -ErrorAction SilentlyContinue | Where-Object {
|
$mingwToolDirs = Get-ChildItem -Path $toolsDir -Directory -ErrorAction SilentlyContinue | Where-Object {
|
||||||
$_.Name -match 'mingw'
|
$_.Name -match '^mingw\d+_\d+$'
|
||||||
}
|
} | Sort-Object -Property @{ Expression = { if ($_.Name -match '_64$') { 1 } else { 0 } }; Descending = $true }, Name -Descending
|
||||||
|
|
||||||
foreach ($dir in $mingwToolDirs) {
|
foreach ($dir in $mingwToolDirs) {
|
||||||
$testBin = Join-Path $dir.FullName "bin\g++.exe"
|
$testBin = Join-Path $dir.FullName "bin\g++.exe"
|
||||||
|
|||||||
@@ -45,9 +45,13 @@ void McpBridge::start() {
|
|||||||
|
|
||||||
void McpBridge::stop() {
|
void McpBridge::stop() {
|
||||||
if (m_client) {
|
if (m_client) {
|
||||||
|
m_client->disconnect(this);
|
||||||
m_client->disconnectFromServer();
|
m_client->disconnectFromServer();
|
||||||
|
m_client->deleteLater();
|
||||||
m_client = nullptr;
|
m_client = nullptr;
|
||||||
}
|
}
|
||||||
|
m_readBuffer.clear();
|
||||||
|
m_initialized = false;
|
||||||
if (m_server) {
|
if (m_server) {
|
||||||
m_server->close();
|
m_server->close();
|
||||||
delete m_server;
|
delete m_server;
|
||||||
@@ -65,8 +69,10 @@ void McpBridge::onNewConnection() {
|
|||||||
|
|
||||||
// Single client — disconnect previous
|
// Single client — disconnect previous
|
||||||
if (m_client) {
|
if (m_client) {
|
||||||
|
m_client->disconnect(this);
|
||||||
m_client->disconnectFromServer();
|
m_client->disconnectFromServer();
|
||||||
m_client->deleteLater();
|
m_client->deleteLater();
|
||||||
|
m_client = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_client = pending;
|
m_client = pending;
|
||||||
@@ -82,10 +88,13 @@ void McpBridge::onNewConnection() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void McpBridge::onReadyRead() {
|
void McpBridge::onReadyRead() {
|
||||||
|
if (!m_client) return;
|
||||||
m_readBuffer.append(m_client->readAll());
|
m_readBuffer.append(m_client->readAll());
|
||||||
|
|
||||||
// Newline-delimited JSON framing
|
// Newline-delimited JSON framing
|
||||||
while (true) {
|
// Guard: processLine→sendJson→flush can re-enter the event loop
|
||||||
|
// and trigger onDisconnected, nulling m_client mid-loop.
|
||||||
|
while (m_client) {
|
||||||
int idx = m_readBuffer.indexOf('\n');
|
int idx = m_readBuffer.indexOf('\n');
|
||||||
if (idx < 0) break;
|
if (idx < 0) break;
|
||||||
QByteArray line = m_readBuffer.left(idx).trimmed();
|
QByteArray line = m_readBuffer.left(idx).trimmed();
|
||||||
@@ -97,7 +106,12 @@ void McpBridge::onReadyRead() {
|
|||||||
|
|
||||||
void McpBridge::onDisconnected() {
|
void McpBridge::onDisconnected() {
|
||||||
qDebug() << "[MCP] Client disconnected";
|
qDebug() << "[MCP] Client disconnected";
|
||||||
m_client = nullptr;
|
if (m_client) {
|
||||||
|
m_client->disconnect(this);
|
||||||
|
m_client->deleteLater();
|
||||||
|
m_client = nullptr;
|
||||||
|
}
|
||||||
|
m_readBuffer.clear();
|
||||||
m_initialized = false;
|
m_initialized = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +141,7 @@ void McpBridge::sendJson(const QJsonObject& obj) {
|
|||||||
qDebug() << "[MCP] >>" << data.left(200);
|
qDebug() << "[MCP] >>" << data.left(200);
|
||||||
data.append('\n');
|
data.append('\n');
|
||||||
m_client->write(data);
|
m_client->write(data);
|
||||||
m_client->flush();
|
if (m_client) m_client->flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
void McpBridge::sendNotification(const QString& method, const QJsonObject& params) {
|
void McpBridge::sendNotification(const QString& method, const QJsonObject& params) {
|
||||||
@@ -172,12 +186,10 @@ void McpBridge::processLine(const QByteArray& line) {
|
|||||||
|
|
||||||
if (method == "initialize") {
|
if (method == "initialize") {
|
||||||
m_mainWindow->setMcpStatus(QStringLiteral("MCP: client connected"));
|
m_mainWindow->setMcpStatus(QStringLiteral("MCP: client connected"));
|
||||||
QCoreApplication::processEvents();
|
|
||||||
sendJson(handleInitialize(id, req.value("params").toObject()));
|
sendJson(handleInitialize(id, req.value("params").toObject()));
|
||||||
m_mainWindow->clearMcpStatus();
|
m_mainWindow->clearMcpStatus();
|
||||||
} else if (method == "tools/list") {
|
} else if (method == "tools/list") {
|
||||||
m_mainWindow->setMcpStatus(QStringLiteral("MCP: tools/list"));
|
m_mainWindow->setMcpStatus(QStringLiteral("MCP: tools/list"));
|
||||||
QCoreApplication::processEvents();
|
|
||||||
sendJson(handleToolsList(id));
|
sendJson(handleToolsList(id));
|
||||||
m_mainWindow->clearMcpStatus();
|
m_mainWindow->clearMcpStatus();
|
||||||
} else if (method == "tools/call") {
|
} else if (method == "tools/call") {
|
||||||
|
|||||||
@@ -473,14 +473,14 @@ QVector<ScanResult> ScanEngine::runScan(std::shared_ptr<Provider> prov,
|
|||||||
<< " filterExec:" << req.filterExecutable
|
<< " filterExec:" << req.filterExecutable
|
||||||
<< " filterWrite:" << req.filterWritable;
|
<< " filterWrite:" << req.filterWritable;
|
||||||
|
|
||||||
// Fallback for providers that don't enumerate regions (file/buffer)
|
// Fallback for providers that don't enumerate regions (file/buffer/syscall without modules)
|
||||||
if (regions.isEmpty()) {
|
if (regions.isEmpty()) {
|
||||||
MemoryRegion fallback;
|
MemoryRegion fallback;
|
||||||
fallback.base = 0;
|
fallback.base = 0;
|
||||||
fallback.size = (uint64_t)prov->size();
|
fallback.size = (uint64_t)prov->size();
|
||||||
fallback.readable = true;
|
fallback.readable = true;
|
||||||
fallback.writable = true;
|
fallback.writable = true;
|
||||||
fallback.executable = false;
|
fallback.executable = true; // unknown; include so filters don't exclude the only region
|
||||||
regions.append(fallback);
|
regions.append(fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -515,7 +515,8 @@ QVector<ScanResult> ScanEngine::runScan(std::shared_ptr<Provider> prov,
|
|||||||
|
|
||||||
constexpr int kChunk = 256 * 1024;
|
constexpr int kChunk = 256 * 1024;
|
||||||
|
|
||||||
for (const auto& region : regions) {
|
for (int regionIndex = 0; regionIndex < regions.size(); ++regionIndex) {
|
||||||
|
const auto& region = regions[regionIndex];
|
||||||
if (m_abort.load()) break;
|
if (m_abort.load()) break;
|
||||||
|
|
||||||
if (req.filterExecutable && !region.executable) continue;
|
if (req.filterExecutable && !region.executable) continue;
|
||||||
@@ -540,7 +541,7 @@ QVector<ScanResult> ScanEngine::runScan(std::shared_ptr<Provider> prov,
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int overlap = patternLen - 1;
|
const int overlap = patternLen; // need full patternLen overlap so pattern at chunk end is found
|
||||||
QByteArray chunk(qMin((uint64_t)kChunk, regSize), Qt::Uninitialized);
|
QByteArray chunk(qMin((uint64_t)kChunk, regSize), Qt::Uninitialized);
|
||||||
uint64_t regOffset = regStart - region.base; // offset within provider region
|
uint64_t regOffset = regStart - region.base; // offset within provider region
|
||||||
|
|
||||||
@@ -552,6 +553,8 @@ QVector<ScanResult> ScanEngine::runScan(std::shared_ptr<Provider> prov,
|
|||||||
|
|
||||||
if (!prov->read(regStart + off, chunk.data(), readLen)) {
|
if (!prov->read(regStart + off, chunk.data(), readLen)) {
|
||||||
// Skip unreadable chunk
|
// Skip unreadable chunk
|
||||||
|
qDebug() << "[scan] read failed region" << regionIndex << "addr" << Qt::showbase << Qt::hex
|
||||||
|
<< (region.base + off) << "base" << region.base << "off" << off << "len" << readLen << Qt::dec;
|
||||||
off += readLen;
|
off += readLen;
|
||||||
scannedBytes += readLen;
|
scannedBytes += readLen;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -790,6 +790,7 @@ private slots:
|
|||||||
QByteArray newBytes(4, '\0');
|
QByteArray newBytes(4, '\0');
|
||||||
std::memcpy(newBytes.data(), &newVal, 4);
|
std::memcpy(newBytes.data(), &newVal, 4);
|
||||||
prov->writeBytes(8, newBytes);
|
prov->writeBytes(8, newBytes);
|
||||||
|
m_panel->valueEdit()->setText("99");
|
||||||
|
|
||||||
// Click update — runs async
|
// Click update — runs async
|
||||||
QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
|
QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
|
||||||
@@ -839,6 +840,7 @@ private slots:
|
|||||||
std::memcpy(nb.data(), &newVal, 4);
|
std::memcpy(nb.data(), &newVal, 4);
|
||||||
prov->writeBytes(i * 4, nb);
|
prov->writeBytes(i * 4, nb);
|
||||||
}
|
}
|
||||||
|
m_panel->valueEdit()->setText("21");
|
||||||
|
|
||||||
// Click Re-scan — runs async
|
// Click Re-scan — runs async
|
||||||
QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
|
QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
|
||||||
@@ -930,6 +932,7 @@ private slots:
|
|||||||
QByteArray nb2(4, '\0');
|
QByteArray nb2(4, '\0');
|
||||||
std::memcpy(nb2.data(), &v2, 4);
|
std::memcpy(nb2.data(), &v2, 4);
|
||||||
prov->writeBytes(4, nb2);
|
prov->writeBytes(4, nb2);
|
||||||
|
m_panel->valueEdit()->setText("20");
|
||||||
{
|
{
|
||||||
QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
|
QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
|
||||||
QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
|
QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
|
||||||
@@ -944,6 +947,7 @@ private slots:
|
|||||||
QByteArray nb3(4, '\0');
|
QByteArray nb3(4, '\0');
|
||||||
std::memcpy(nb3.data(), &v3, 4);
|
std::memcpy(nb3.data(), &v3, 4);
|
||||||
prov->writeBytes(4, nb3);
|
prov->writeBytes(4, nb3);
|
||||||
|
m_panel->valueEdit()->setText("30");
|
||||||
{
|
{
|
||||||
QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
|
QSignalSpy rescanSpy(m_panel->engine(), &ScanEngine::rescanFinished);
|
||||||
QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
|
QTest::mouseClick(m_panel->updateButton(), Qt::LeftButton);
|
||||||
@@ -1009,6 +1013,7 @@ private slots:
|
|||||||
int32_t newVal = kVal + iter;
|
int32_t newVal = kVal + iter;
|
||||||
for (int off = 0; off + 4 <= kBufSize; off += kStride)
|
for (int off = 0; off + 4 <= kBufSize; off += kStride)
|
||||||
std::memcpy(prov->data().data() + off, &newVal, 4);
|
std::memcpy(prov->data().data() + off, &newVal, 4);
|
||||||
|
m_panel->valueEdit()->setText(QString::number(newVal));
|
||||||
|
|
||||||
QElapsedTimer iterTimer;
|
QElapsedTimer iterTimer;
|
||||||
iterTimer.start();
|
iterTimer.start();
|
||||||
|
|||||||
Reference in New Issue
Block a user