# A13 — debug: glibc strerror_r return value ignored; uninitialised buffer printed

Bug ref      : always.md A.13 ; pharo.md §5.7
Severity     : MEDIUM (info leak of stack to log on every error path on glibc)
File         : src/debug.c
Lines (HEAD) : 57-67 (`logMessageFromErrno`)

## Problem

```c
void logMessageFromErrno(int level, const char* msg, const char* fileName, const char* functionName, int line){
	char buffer[1024+1];

#ifdef WIN32
	strerror_s(buffer, 1024, errno);
#else
	strerror_r(errno, buffer, 1024);
#endif

	logMessage(level, fileName, functionName, line, "%s: %s", msg, buffer);
}
```

The Linux/glibc default for `strerror_r` is the **GNU variant**:

    char *strerror_r(int errnum, char *buf, size_t buflen);

which returns a pointer to the error string and is **not required to
write into `buf`** for known errno values (it can hand back an
internal static string instead). The caller's `buffer` therefore
contains uninitialised stack bytes that this code unconditionally
prints via `%s`. The printed bytes may include local pointers, stack
canaries, or other secrets, depending on prior stack contents.

(The POSIX/XSI variant returns `int` and always writes `buf`, but
selecting that variant requires `-D_POSIX_C_SOURCE=200112L` or a
`#define` dance before `<string.h>` — which this file does not do.)

## Fix

Consume the return value. On systems that picked up the GNU variant,
use the returned pointer to format the message; on systems that
selected the POSIX variant, use the buffer (which is guaranteed
written when the return value is 0).

```diff
diff --git a/src/debug.c b/src/debug.c
index be1afeaf6..852ad899a 100644
--- a/src/debug.c
+++ b/src/debug.c
@@ -56,14 +56,27 @@ EXPORT(void) logAssert(const char* fileName, const char* functionName, int line,
 
 void logMessageFromErrno(int level, const char* msg, const char* fileName, const char* functionName, int line){
 	char buffer[1024+1];
+	const char *errstr;
 
 #ifdef WIN32
 	strerror_s(buffer, 1024, errno);
+	errstr = buffer;
+#elif (defined(__GLIBC__) && defined(_GNU_SOURCE))
+	/* GNU strerror_r returns a pointer that may not write to buffer */
+	buffer[0] = '\0';
+	errstr = strerror_r(errno, buffer, sizeof(buffer));
+	if (errstr == NULL) {
+		errstr = "unknown error";
+	}
 #else
-	strerror_r(errno, buffer, 1024);
+	/* POSIX/XSI strerror_r: writes buffer and returns int */
+	if (strerror_r(errno, buffer, sizeof(buffer)) != 0) {
+		snprintf(buffer, sizeof(buffer), "errno %d", errno);
+	}
+	errstr = buffer;
 #endif
 
-	logMessage(level, fileName, functionName, line, "%s: %s", msg, buffer);
+	logMessage(level, fileName, functionName, line, "%s: %s", msg, errstr);
 }
 
 FILE* getStreamForLevel(int level){
```

## Test plan

- Force `errno = EINVAL`; call `logMessageFromErrno(LOG_ERROR,
  "test", ...)`. Expected: "test: Invalid argument" (or the
  platform-localised equivalent).
- Pre-fill the stack with `0x41` before the call (e.g. by calling a
  helper that writes a 4 KiB stack array). Before: 'A's leaked into
  the log on glibc; after: a sensible error string.
- Build under `-fsanitize=memory`. Before: MSAN reports "use of
  uninitialized value" inside `vfprintf`; after: clean.

## Risk notes

- The preprocessor branch follows what `<string.h>` does internally
  to select the variant on glibc. If a future glibc changes the
  feature test, the POSIX branch is a safe fallback.
- Behaviour is unchanged on Windows (`strerror_s` always writes the
  buffer) and on musl/BSD (always uses the POSIX variant).
