From b92d2ae451a9c6cae3a29f294d52684b4aa56941 Mon Sep 17 00:00:00 2001 From: David Sancho Moreno Date: Wed, 4 Mar 2026 18:04:20 +0000 Subject: [PATCH] fix: validate console handles on Windows to prevent segfault On some Windows environments (e.g. GitHub Actions runners), _isatty() returns true for handles that are not real console screen buffers (likely due to ConPTY). Passing these handles to GetConsoleScreenBufferInfo causes an access violation (segfault). This adds two layers of defense: 1. C stub level: validate handles with GetConsoleMode before calling GetConsoleScreenBufferInfo/SetConsoleWindowInfo. Invalid handles now raise a Unix_error instead of crashing. 2. OCaml level: wrap the get_size_from_fd call in LTerm.create with a try/catch. If getting the terminal size fails, fall back to treating the terminal as non-tty instead of crashing. Fixes #124 --- src/lTerm.ml | 28 ++++++++++++++++++---------- src/lTerm_term_stubs.c | 27 +++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/lTerm.ml b/src/lTerm.ml index 32770d69..9df007ac 100644 --- a/src/lTerm.ml +++ b/src/lTerm.ml @@ -210,16 +210,24 @@ let create term.input_stream <- Lwt_stream.from (fun () -> Lwt_io.read_char_opt term.ic); (* Setup initial size and size updater. *) if term.outgoing_is_a_tty then begin - let check_size () = - let size = get_size_from_fd term.outgoing_fd in - if size <> term.size then begin - term.size <- size; - Lwt_condition.signal term.notify (LTerm_event.Resize size) - end - in - term.size <- get_size_from_fd term.outgoing_fd; - term.last_reported_size <- term.size; - term.event <- E.map check_size resize_event + try + let check_size () = + try + let size = get_size_from_fd term.outgoing_fd in + if size <> term.size then begin + term.size <- size; + Lwt_condition.signal term.notify (LTerm_event.Resize size) + end + with Unix.Unix_error _ -> () + in + term.size <- get_size_from_fd term.outgoing_fd; + term.last_reported_size <- term.size; + term.event <- E.map check_size resize_event + with Unix.Unix_error _ -> + (* If we can't get the terminal size (e.g. on Windows when the + handle passes isatty but is not a real console), fall back to + treating it as a non-tty. *) + term.outgoing_is_a_tty <- false end; return term) Lwt.fail diff --git a/src/lTerm_term_stubs.c b/src/lTerm_term_stubs.c index 002c36da..20901b3a 100644 --- a/src/lTerm_term_stubs.c +++ b/src/lTerm_term_stubs.c @@ -23,10 +23,22 @@ CAMLprim value lt_term_get_size_from_fd(value fd) { + HANDLE h = Handle_val(fd); CONSOLE_SCREEN_BUFFER_INFO info; + DWORD mode; value result; - if (!GetConsoleScreenBufferInfo(Handle_val(fd), &info)) { + /* Validate that the handle is a usable console handle before calling + GetConsoleScreenBufferInfo. On some Windows environments (e.g. GitHub + Actions runners using ConPTY), _isatty() may return true for handles + that are not real console screen buffers, causing + GetConsoleScreenBufferInfo to crash with an access violation. */ + if (h == INVALID_HANDLE_VALUE || h == NULL || !GetConsoleMode(h, &mode)) { + win32_maperr(ERROR_INVALID_HANDLE); + uerror("GetConsoleScreenBufferInfo", Nothing); + } + + if (!GetConsoleScreenBufferInfo(h, &info)) { win32_maperr(GetLastError()); uerror("GetConsoleScreenBufferInfo", Nothing); } @@ -39,21 +51,28 @@ CAMLprim value lt_term_get_size_from_fd(value fd) CAMLprim value lt_term_set_size_from_fd(value fd, value val_size) { + HANDLE h = Handle_val(fd); CONSOLE_SCREEN_BUFFER_INFO info; SMALL_RECT rect; + DWORD mode; + + /* Validate that the handle is a usable console handle. */ + if (h == INVALID_HANDLE_VALUE || h == NULL || !GetConsoleMode(h, &mode)) { + win32_maperr(ERROR_INVALID_HANDLE); + uerror("SetConsoleWindowInfo", Nothing); + } - if (!GetConsoleScreenBufferInfo(Handle_val(fd), &info)) { + if (!GetConsoleScreenBufferInfo(h, &info)) { win32_maperr(GetLastError()); uerror("GetConsoleScreenBufferInfo", Nothing); } - rect; rect.Top = info.srWindow.Top; rect.Left = info.srWindow.Left; rect.Bottom = rect.Top + Int_val(Field(val_size, 0)) - 1; rect.Right = rect.Left + Int_val(Field(val_size, 1)) - 1; - if (!SetConsoleWindowInfo(Handle_val(fd), TRUE, &rect)) { + if (!SetConsoleWindowInfo(h, TRUE, &rect)) { win32_maperr(GetLastError()); uerror("SetConsoleWindowInfo", Nothing); }