fix(discovery): tolerate CRLF line endings in mtree parsing

mtree_low_split anchored the system flatview on "Root memory region: system"
followed by LF/space/EOF, but QEMU's HMP `info mtree -f` output is CRLF, so the
byte after "system" is '\r'. The anchor was rejected, the parser returned 0
(fail-closed), and on a real guest the daemon never attached the VM (low=0 =>
ok=0). The synthetic LF-only fixture hid this; the fix is verified against the
real CRLF output.

Accept '\r' in the anchor check (LF-only input still works) and add a regression
test that re-encodes the fixture as CRLF in memory.

Bump 0.3.7.
This commit is contained in:
2026-06-24 15:08:07 +03:00
parent 3142337e62
commit 929bcf0e74
3 changed files with 27 additions and 4 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
# Single source of truth for the version: CI passes -DVMSIG_VERSION=${TAG#v}, so the project # Single source of truth for the version: CI passes -DVMSIG_VERSION=${TAG#v}, so the project
# version (-> libvgpu-perception SONAME/.so version) and the .deb version come from one tag. # version (-> libvgpu-perception SONAME/.so version) and the .deb version come from one tag.
set(VMSIG_VERSION "0.3.6" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag") set(VMSIG_VERSION "0.3.7" CACHE STRING "Release version (MAJOR.MINOR.PATCH); CI passes the tag")
project(vmsig VERSION ${VMSIG_VERSION} LANGUAGES C) project(vmsig VERSION ${VMSIG_VERSION} LANGUAGES C)
set(CMAKE_C_STANDARD 17) set(CMAKE_C_STANDARD 17)
+4 -3
View File
@@ -95,10 +95,11 @@ static const char* find_system_flatview(const char* text, const char** body_end)
const char* anchor = "Root memory region: system"; const char* anchor = "Root memory region: system";
const char* p = text; const char* p = text;
while ((p = strstr(p, anchor)) != NULL) { while ((p = strstr(p, anchor)) != NULL) {
/* The root name must end the token (newline/EOF) — reject "system.flash0" etc., /* The root name must end the token (CR/LF/space/EOF) — reject "system.flash0" etc.,
* and reject roots that merely contain the word elsewhere. */ * and reject roots that merely contain the word elsewhere. QEMU's HMP output is
* CRLF, so the byte after "system" is '\r'; accept it (LF-only input also works). */
const char* after = p + strlen(anchor); const char* after = p + strlen(anchor);
if (*after == '\n' || *after == '\0' || *after == ' ') { if (*after == '\n' || *after == '\0' || *after == ' ' || *after == '\r') {
const char* body = strchr(p, '\n'); const char* body = strchr(p, '\n');
if (!body) return NULL; if (!body) return NULL;
body++; /* first region line */ body++; /* first region line */
+22
View File
@@ -32,6 +32,18 @@ static char* slurp(const char* path) {
return buf; return buf;
} }
/* Re-encode every '\n' as '\r\n' (QEMU's HMP output is CRLF). Caller frees; NULL on OOM. */
static char* to_crlf(const char* lf) {
size_t n = 0, extra = 0;
for (const char* p = lf; *p; p++) { n++; if (*p == '\n') extra++; }
char* out = malloc(n + extra + 1);
if (!out) return NULL;
char* o = out;
for (const char* p = lf; *p; p++) { if (*p == '\n') *o++ = '\r'; *o++ = *p; }
*o = 0;
return out;
}
/* Case B: a minimal, NON-fragmented system flatview — one big GPA-0 ram run plus high-RAM /* Case B: a minimal, NON-fragmented system flatview — one big GPA-0 ram run plus high-RAM
* carrying @<low>. Must not be broken by the new parser. */ * carrying @<low>. Must not be broken by the new parser. */
static const char* k_happy = static const char* k_happy =
@@ -65,6 +77,16 @@ int main(void) {
/* E: the decoy (non-system) flatview comes FIRST and carries @0x40000000; the /* E: the decoy (non-system) flatview comes FIRST and carries @0x40000000; the
* function must select the SYSTEM flatview (@0x80000000), not the decoy. */ * function must select the SYSTEM flatview (@0x80000000), not the decoy. */
CHECK(low != 0x40000000ull, "E: decoy flatview @offset rejected (system AS chosen)"); CHECK(low != 0x40000000ull, "E: decoy flatview @offset rejected (system AS chosen)");
/* F: real QEMU HMP output is CRLF. The parser MUST tolerate '\r' — a synthetic
* LF-only fixture hid this, so the shipped parser returned 0 on the real VM mtree
* (-> low=0 -> VM never attached). Regression guard, independent of how git stores
* the fixture's line endings. */
char* frag_crlf = to_crlf(frag);
CHECK(frag_crlf != NULL, "F: CRLF copy allocated");
if (frag_crlf) {
CHECK(mtree_low_split(frag_crlf) == 0x80000000ull, "F: CRLF fragmented split == 0x80000000");
free(frag_crlf);
}
free(frag); free(frag);
} }