Fix fdsan violation in StdoutLogger::Stop() on Android API 29+#17
Merged
CedricGuillemet merged 1 commit intoBabylonJS:mainfrom Apr 21, 2026
Merged
Conversation
StdoutLogger::Start() calls fdopen(fd[0], "r") to create a FILE* for each reader thread. Per POSIX, fdopen() transfers ownership of the file descriptor to the FILE*; the caller must use fclose() — not close() — to release it. StdoutLogger::Stop() was calling close(fd_stdout[0]) and close(fd_stderr[0]) directly. On Android API 29+, fdsan enforces ownership tracking and aborts with SIGABRT when a raw close() is called on a descriptor owned by a FILE*: Fatal signal 6 (SIGABRT) Abort message: 'fdsan: attempted to close file descriptor 94, expected to be unowned, actually owned by FILE* 0x6e357db180' Fix: in Stop(), only close the write ends (fd[1]). Closing fd[1] sends EOF through the pipe, causing getline() in the reader thread to return -1, which breaks the loop and calls fclose(stream) — correctly closing fd[0]. Remove the explicit close(fd[0]) / close(fd[1]) calls entirely.
CedricGuillemet
added a commit
to CedricGuillemet/JsRuntimeHost
that referenced
this pull request
Apr 20, 2026
Replace the local PATCH_COMMAND workaround with a direct reference to the fix commit on the fork (PR: BabylonJS/AndroidExtensions#17). Remove patches/AndroidExtensions/StdoutLogger.cpp. Once the PR is merged upstream, revert GIT_REPOSITORY back to BabylonJS/AndroidExtensions and update GIT_TAG to the merge commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR aims to prevent Android API 29+ fdsan aborts by avoiding close() on pipe read-ends that have been transferred to FILE* ownership via fdopen() in StdoutLogger.
Changes:
- Updates
StdoutLogger::Stop()to close only the pipe write-ends and stop directly closing pipe read-ends. - Adds explanatory comments about
fdopen()ownership and fdsan behavior on Android API 29+.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
bkaradzic-microsoft
approved these changes
Apr 20, 2026
bghgary
approved these changes
Apr 20, 2026
Contributor
bghgary
left a comment
There was a problem hiding this comment.
[Approved by Copilot on behalf of @bghgary]
LGTM.
A few pre-existing issues in this file, not introduced here and not blocking, but worth tracking as follow-ups:
- Detached reader threads are never joined —
Stop()returns while they may still be draining. - Original
stdout/stderrfds aren't saved, so writes afterStop()hit a closed pipe (EPIPE). pthread_createerror check compares against-1, but it returns an errno code on failure; the error path also leaks the pipe fds.pipe()'s return value is ignored too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
StdoutLogger::Start()callsfdopen(fd[0], "r")to hand each pipe read-end to a reader thread.fdopen()transfers ownership of the file descriptor to theFILE*; it must be released withfclose(), notclose().StdoutLogger::Stop()was callingclose(fd_stdout[0])andclose(fd_stderr[0])directly. On Android API 29+, fdsan tracks descriptor ownership and aborts withSIGABRTwhen a rawclose()is called on a descriptor owned by aFILE*:Fix
In
Stop(), only close the write ends (fd[1]). Closingfd[1]sends EOF through the pipe;getline()in the reader thread returns-1, exits the loop, and callsfclose(stream)— which correctly releases the read end. The explicitclose(fd[0])calls are removed entirely.