Skip to content

Add guards against invalid file descriptors#931

Closed
joshvoigts wants to merge 1 commit intokkawakam:masterfrom
joshvoigts:fix/invalid-fd-guards
Closed

Add guards against invalid file descriptors#931
joshvoigts wants to merge 1 commit intokkawakam:masterfrom
joshvoigts:fix/invalid-fd-guards

Conversation

@joshvoigts
Copy link
Copy Markdown

This PR adds guards against invalid file descriptors (-1) in src/tty/unix.rs to prevent panics.

The problem:
I was hitting this panic:

PANIC: panicked at /Users/joshvoigts/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rustyline-17.0.2/src/tty/unix.rs:83:18:
fd != -1

The issue seemed to come up when resizing terminal splits, and occasionally when quitting. After some digging, I found that certain code paths could end up passing an invalid file descriptor (-1).

The fix:
Add a few defensive checks.

@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 23, 2026

I would prefer to fix the root cause instead of its consequences

@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 23, 2026

Could you please confirm that you don't use signal-hook feature ?
And I am going to assume that the only file descriptor that can be invalid is:
SIG_PIPE
Which means that the only place that can panic is:

let _ = unsafe { write(SIG_PIPE, &[b]) };

@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 23, 2026

Could you please give #932 a try ?

@joshvoigts
Copy link
Copy Markdown
Author

Sure, I'll give it a shot. It's pretty intermittent. I'll be using the library for awhile and then inexplicably hit the panic. I haven't quite narrowed down when it happens but it seems like it happens around terminal split pane resizes or maybe ctrl+d?

@joshvoigts
Copy link
Copy Markdown
Author

joshvoigts commented Mar 24, 2026

I'm seeing what looks like another issue with #932:

PANIC: panicked at /Users/joshvoigts/Desktop/repos/rustyline/src/tty/unix.rs:1301:9:
assertion failed: unsafe { SIG_PIPE }.is_none()

It could be because in some circumstances rustyline editor is reinitialized. I'll try without the assert.

@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 25, 2026

I'm seeing what looks like another issue with #932:

PANIC: panicked at /Users/joshvoigts/Desktop/repos/rustyline/src/tty/unix.rs:1301:9:
assertion failed: unsafe { SIG_PIPE }.is_none()

It could be because in some circumstances rustyline editor is reinitialized. I'll try without the assert.

You should not !
You should try to find why uninstall_sigwinch_handler is not called (or provide a minimal example to reproduce the issue):

Index: src/tty/unix.rs
===================================================================
diff --git a/src/tty/unix.rs b/src/tty/unix.rs
--- a/src/tty/unix.rs	(revision 12ea5243e4657b2d0ceb07075cc88102b654b412)
+++ b/src/tty/unix.rs	(date 1774420169850)
@@ -163,6 +163,7 @@
 #[expect(unused_must_use)]
 impl Drop for TtyIn {
     fn drop(&mut self) {
+        debug!(target: "rustyline", "TtyIn::drop");
         if let Some(sig) = self.sig.take() {
             sig.uninstall_sigwinch_handler();
         }
@@ -1298,6 +1299,7 @@
 impl Sig {
     #[cfg(not(feature = "signal-hook"))]
     fn install_sigwinch_handler() -> Result<Self> {
+        debug!(target: "rustyline", "install_sigwinch_handler");
         assert!(unsafe { SIG_PIPE }.is_none());
         use nix::sys::signal;
         let (pipe, pipe_write) = UnixStream::pair()?;
@@ -1310,6 +1312,7 @@
         );
         let original_sigint = unsafe { signal::sigaction(signal::SIGINT, &sa)? };
         let original_sigwinch = unsafe { signal::sigaction(signal::SIGWINCH, &sa)? };
+        debug!(target: "rustyline", "install_sigwinch_handler: {pipe:?}");
         Ok(Self {
             pipe,
             original_sigint,
@@ -1328,6 +1331,7 @@
     #[cfg(not(feature = "signal-hook"))]
     fn uninstall_sigwinch_handler(self) -> Result<()> {
         use nix::sys::signal;
+        debug!(target: "rustyline", "uninstall_sigwinch_handler: {self.pipe:?}");
         let _ = unsafe { signal::sigaction(signal::SIGINT, &self.original_sigint)? };
         let _ = unsafe { signal::sigaction(signal::SIGWINCH, &self.original_sigwinch)? };
         close(self.pipe)?;

Make sure a logger is initialized:

fn main() ... {
    env_logger::init();
...

And something like:

RUST_LOG=rustyline=debug cargo run --example example 2> debug.log

@joshvoigts
Copy link
Copy Markdown
Author

2026-03-26T04:18:38.496738Z DEBUG rustyline: GraphemeClusterMode: WcWidth
2026-03-26T04:18:38.497105Z DEBUG rustyline: PathInfo("/Users/joshvoigts/readline.txt", SystemTime { tv_sec: 1774497920, tv_nsec: 241586949 }, 100)
2026-03-26T04:18:38.498542Z DEBUG rustyline: VEOF: KeyEvent(Char('D'), Modifiers(CTRL))
2026-03-26T04:18:38.498570Z DEBUG rustyline: VINTR: KeyEvent(Char('C'), Modifiers(CTRL))
2026-03-26T04:18:38.498578Z DEBUG rustyline: VQUIT: KeyEvent(Char('\\'), Modifiers(CTRL))
2026-03-26T04:18:38.498585Z DEBUG rustyline: VSUSP: KeyEvent(Char('Z'), Modifiers(CTRL))
2026-03-26T04:18:38.498644Z DEBUG rustyline: Changeset::delete(0, "")
2026-03-26T04:18:38.498652Z DEBUG rustyline: Changeset::insert_str(0, "")
2026-03-26T04:18:38.498668Z DEBUG rustyline: install_sigwinch_handler
2026-03-26T04:18:38.498682Z DEBUG rustyline: install_sigwinch_handler: UnixStream { fd: FileDesc(OwnedFd { fd: 10 }), local: (unnamed), peer: (unnamed) }
2026-03-26T04:18:38.498717Z DEBUG rustyline: old layout: Layout { grapheme_cluster_mode: WcWidth, prompt_size: Position { col: 0, row: 0 }, default_prompt: false, cursor: Position { col: 0, row: 0 }, end: Position { col: 0, row: 0 }, has_info: false }
2026-03-26T04:18:38.498760Z DEBUG rustyline: new layout: Layout { grapheme_cluster_mode: WcWidth, prompt_size: Position { col: 9, row: 0 }, default_prompt: true, cursor: Position { col: 9, row: 0 }, end: Position { col: 9, row: 0 }, has_info: false }
remote > 2026-03-26T04:18:40.738474Z DEBUG rustyline: read buffer [91, 65]
2026-03-26T04:18:40.738529Z DEBUG rustyline: c: '\u{1b}' => key: KeyEvent(Up, Modifiers(0x0))
2026-03-26T04:18:40.738752Z DEBUG rustyline: Emacs command: LineUpOrPreviousHistory(1)
2026-03-26T04:18:40.738819Z DEBUG rustyline: Changeset::begin
2026-03-26T04:18:40.738853Z DEBUG rustyline: Changeset::delete(0, "")
2026-03-26T04:18:40.738879Z DEBUG rustyline: Changeset::insert_str(0, "do u have access to a web fetch tool?")
2026-03-26T04:18:40.738919Z DEBUG rustyline: Changeset::end
2026-03-26T04:18:40.739011Z DEBUG rustyline: old layout: Layout { grapheme_cluster_mode: WcWidth, prompt_size: Position { col: 9, row: 0 }, default_prompt: true, cursor: Position { col: 9, row: 0 }, end: Position { col: 9, row: 0 }, has_info: false }
2026-03-26T04:18:40.739064Z DEBUG rustyline: new layout: Layout { grapheme_cluster_mode: WcWidth, prompt_size: Position { col: 9, row: 0 }, default_prompt: true, cursor: Position { col: 46, row: 0 }, end: Position { col: 46, row: 0 }, has_info: false }
remote > do u have access to a web fetch tool?2026-03-26T04:18:42.119881Z DEBUG rustyline: c: '\r' => key: KeyEvent(Enter, Modifiers(0x0))
2026-03-26T04:18:42.120030Z DEBUG rustyline: Changeset::insert(37, '\n')
2026-03-26T04:18:42.120149Z DEBUG rustyline: old layout: Layout { grapheme_cluster_mode: WcWidth, prompt_size: Position { col: 9, row: 0 }, default_prompt: true, cursor: Position { col: 46, row: 0 }, end: Position { col: 46, row: 0 }, has_info: false }
2026-03-26T04:18:42.120215Z DEBUG rustyline: new layout: Layout { grapheme_cluster_mode: WcWidth, prompt_size: Position { col: 9, row: 0 }, default_prompt: true, cursor: Position { col: 0, row: 1 }, end: Position { col: 0, row: 1 }, has_info: false }
remote > do u have access to a web fetch tool?
2026-03-26T04:18:42.687099Z DEBUG rustyline: c: '\r' => key: KeyEvent(Enter, Modifiers(0x0))
2026-03-26T04:18:42.687249Z DEBUG rustyline: Changeset::begin
2026-03-26T04:18:42.687346Z DEBUG rustyline: Changeset::end
2026-03-26T04:18:42.687467Z DEBUG rustyline: TtyIn::drop
2026-03-26T04:18:42.687491Z DEBUG rustyline: uninstall_sigwinch_handler: UnixStream { fd: FileDesc(OwnedFd { fd: 10 }), local: (unnamed), peer: (unnamed) }

No, I don't have access to a web fetch tool. My available tools are limited to file operations:

file_view - view files/directories
file_create - create files/directories
file_delete - delete files/directories
file_search - search files by name or content
file_search_replace - edit files

Is there something specific you're trying to accomplish?
2026-03-26T04:18:46.224210Z DEBUG rustyline: VEOF: KeyEvent(Char('D'), Modifiers(CTRL))
2026-03-26T04:18:46.224245Z DEBUG rustyline: VINTR: KeyEvent(Char('C'), Modifiers(CTRL))
2026-03-26T04:18:46.224258Z DEBUG rustyline: VQUIT: KeyEvent(Char('\\'), Modifiers(CTRL))
2026-03-26T04:18:46.224270Z DEBUG rustyline: VSUSP: KeyEvent(Char('Z'), Modifiers(CTRL))
2026-03-26T04:18:46.224334Z DEBUG rustyline: Changeset::delete(0, "")
2026-03-26T04:18:46.224348Z DEBUG rustyline: Changeset::insert_str(0, "")
2026-03-26T04:18:46.224359Z DEBUG rustyline: install_sigwinch_handler
PANIC: panicked at /Users/joshvoigts/Desktop/repos/rustyline/src/tty/unix.rs:1303:9:
assertion failed: unsafe { SIG_PIPE }.is_none()

@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 26, 2026

It seems that https://github.com/kkawakam/rustyline/pull/932/changes#diff-bc8fd5ba686c57cce3f8eefc70d2b5419468e9c84b5d1923095584f8dabb010bR1334

unsafe { SIG_PIPE }.take()

doesn't work as expected
Or an error happens before. Maybe here:

close(self.pipe)?;

@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 26, 2026

A minimal example to reproduce the issue:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=36c59c4bf882aebaaeb14aa818e1c29e
My bad:

unsafe { SIG_PIPE }.take()

cannot be used !

@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 26, 2026

Could you please give #932 another try ?

@joshvoigts
Copy link
Copy Markdown
Author

Sorry, I didn't see you force pushed, trying, sometimes it randomly comes up, so I might try it for a few days. It's working so far.

@joshvoigts
Copy link
Copy Markdown
Author

I've been running #932 for a few days and it's working well for me. I'll close this pr. Appreciate the help.

@joshvoigts joshvoigts closed this Mar 28, 2026
@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 28, 2026

Thank for your time / feedback.

@gwenn
Copy link
Copy Markdown
Collaborator

gwenn commented Mar 29, 2026

Version 18.0.0 released

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants