Possible out-of-bounds read in `gocv.GetLastExceptionMessage` via a non-NUL-terminated 1024-byte global
Possible out-of-bounds read in gocv.GetLastExceptionMessage via a non-NUL-terminated 1024-byte global
I found a possible out-of-bounds read in GetLastExceptionMessage. setExceptionInfo copies the
OpenCV exception text into a fixed 1024-byte global using strncpy(dst, src, 1024) — a size equal
to the full buffer, which per the C standard leaves the destination unterminated whenever the
source is 1024 bytes or longer. The Go side then reads that global with C.GoString, which scans
for a NUL byte with no length bound. If an exception message reaches 1024 bytes, the scan runs past
the end of the global and the adjacent data-segment bytes are copied into the returned Go string.
File: core.go (Go side), core.cpp (C++ side)
Function: gocv.io/x/gocv.GetLastExceptionMessage (core.go:22-24)
// GetLastExceptionMessage returns the last exception message from the OpenCV library.
func GetLastExceptionMessage() string {
return C.GoString(C.GetOpenCVExceptionMessage())
}core.cpp:4-23 — the buffer, the writer and the accessor:
int lastException = 0;
char lastExceptionMessage[1024];
int GetOpenCVException() {
return lastException;
}
const char* GetOpenCVExceptionMessage() {
return lastExceptionMessage;
}
void ClearOpenCVException() {
lastException = 0;
strncpy(lastExceptionMessage, "", 1024);
}
void setExceptionInfo(int code, const char* message) {
lastException = code;
strncpy(lastExceptionMessage, message, 1024);
}A representative producer, imgcodecs.cpp:8-11 (there are ~250 such sites across the C++ layer):
} catch(const cv::Exception& e){
setExceptionInfo(e.code, e.what());- Go calls a wrapped OpenCV function whose failure message embeds caller-controlled text — most
directly a long filesystem path (
gocv.IMRead,ReadNetwith a model path, etc.). - The C++ wrapper catches
cv::Exceptionand callssetExceptionInfo(e.code, e.what()). strncpy(lastExceptionMessage, message, 1024)writes at most 1024 bytes and appends a terminator only ifstrlen(message) < 1024. Withnequal to the full buffer size, a message of 1024 bytes or more fills all 1024 bytes and leaves no NUL anywhere in the buffer. (strncpystays in bounds here — the write itself is fine; only the termination is missing.)GetOpenCVExceptionMessage()returns the barechar *to Go with no length.OpenCVResultcarries aLengthfield (core.h:271) but it is not used on this path, and the Go side appliesC.GoString, whose contract is "read until the first NUL" — no bound, noC.GoStringN.C.GoStringtherefore reads past the end of the 1024-byte global into whatever follows it in the data/BSS segment until it happens upon a zero byte, and materialises those bytes into the Go string that the application logs or returns — an out-of-bounds read plus disclosure of adjacent process memory.
Note that ClearOpenCVException does zero-fill the buffer (strncpy(dst, "", 1024) pads the whole
1024 bytes with NULs), but it must be called, and setExceptionInfo subsequently overwrites all
1024 bytes with no terminator. Its only caller is LastExceptionError (core.go:32-39), which
defers ClearLastException() and so runs it after GetLastExceptionMessage() at line 38 — that
is, after the over-read has already happened.
Go trigger (if applicable):
// A path long enough that OpenCV's formatted message exceeds 1024 bytes.
longPath := "/tmp/" + strings.Repeat("a", 1200) + ".png"
img := gocv.IMRead(longPath, gocv.IMReadColor) // raises cv::Exception in the C++ layer
defer img.Close()
fmt.Printf("%q\n", gocv.GetLastExceptionMessage())
// The returned string is longer than 1024 bytes and its tail is whatever
// data-segment bytes follow lastExceptionMessage.A long path is the simplest lever; a long DNN layer/blob name or a long assertion expression also
works. The read is fully synchronous — setExceptionInfo and GetLastExceptionMessage run on the
same call, so no interleaving is needed, and no GC interaction is involved. It is not
platform-specific: strncpy's no-terminator-on-truncation behaviour is standard C (C17 §7.24.2.4).
Separately, lastExceptionMessage is a process-global with no locking, so concurrent Go calls into
OpenCV race on it; that is a distinct issue from the over-read reported here.
Suggested fix: guarantee termination on the C side, e.g.
void setExceptionInfo(int code, const char* message) {
lastException = code;
strncpy(lastExceptionMessage, message, sizeof(lastExceptionMessage) - 1);
lastExceptionMessage[sizeof(lastExceptionMessage) - 1] = '\0';
}(or use snprintf). Bounding the read on the Go side as well —
C.GoStringN(C.GetOpenCVExceptionMessage(), 1024) with trailing-NUL trimming — would make the Go
wrapper safe independently of the C fix.
Source: hybridgroup/gocv