bug: Shell server (vsock) does not allocate PTY interactive signal handling broken #70

Closed
opened 2026-03-25 22:53:18 +00:00 by mahmoud · 3 comments
Owner

Description

The shell server in my_hypervisor-init listening on vsock port 1025 runs an interactive shell without allocating a pseudo-terminal (PTY). This breaks interactive terminal behavior when connecting via the web console or my_hypervisor attach.

Steps to Reproduce

Option A: Via Hero Compute web console

  1. Install and start Hero Compute on a bare-metal server with KVM:
git clone https://forge.ourworld.tf/lhumina_code/hero_compute.git
cd hero_compute                                                                                            
make configure                                            
make start
  1. Open the dashboard at http://:9001
  2. Set a secret in Settings
  3. Go to My VMs and click Deploy VM
  4. Select an image (e.g., ubuntu-24.04:latest), name the VM, choose 1 slice, and deploy
  5. Wait for deployment to complete (VM state shows "running")
  6. Click the Console button (terminal icon) on the VM row
  7. The web terminal opens and shows a shell prompt
  8. Type ping 1.1.1.1 and press Enter
  9. Wait for a few ping responses to appear
  10. Press Ctrl+C to interrupt the ping

Option B: Via chvm CLI

  1. Create and start a VM:
my_hypervisor run --name test-pty --cpus 1 --memory 512 --detach forge.ourworld.tf/lhumina_code/hero_compute_registry/ubuntu-24.04:latest                                 
  1. Attach to the VM shell:
    my_hypervisor attach test-pty
  2. Type ping 1.1.1.1 and press Enter
  3. Wait for a few ping responses to appear
  4. Press Ctrl+C to interrupt the ping

Option C: Via raw vsock connection

  1. Create and start a VM as in Option B
  2. Connect directly to the shell server via the vsock socket:
    echo "CONNECT 1025" | socat - UNIX-CONNECT:~/.chvm/vms//vsock.sock
  3. Type ping 1.1.1.1 and press Enter
  4. Press Ctrl+C

Actual Result

ping 1.1.1.1
PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data.
64 bytes from 1.1.1.1: icmp_seq=1 ttl=56 time=5.49 ms
64 bytes from 1.1.1.1: icmp_seq=2 ttl=56 time=5.60 ms
^C^C64 bytes from 1.1.1.1: icmp_seq=3 ttl=56 time=5.53 ms
^C^C^C^C^C64 bytes from 1.1.1.1: icmp_seq=4 ttl=56 time=5.51 ms
^C^C^C64 bytes from 1.1.1.1: icmp_seq=5 ttl=56 time=5.57 ms

  • The \x03 byte (Ctrl+C) is echoed as ^C in the output
  • The ping process continues running and is not interrupted
  • The user cannot stop any foreground process with Ctrl+C
  • The only way to stop ping is to close the terminal/connection entirely

Expected Result

  • Pressing Ctrl+C once should immediately interrupt the ping process
  • Ping should display its statistics summary and return to the shell prompt:
    64 bytes from 1.1.1.1: icmp_seq=2 ttl=56 time=5.60 ms
    ^C
    --- 1.1.1.1 ping statistics ---
    2 packets transmitted, 2 received, 0% packet loss, time 1001ms
    rtt min/avg/max/mdev = 5.490/5.545/5.600/0.055 ms
    root@vm:~#

Root Cause

The shell server spawns a shell process (/bin/bash or /bin/sh) with raw stdin/stdout pipes but no PTY.
Without a PTY:

  • The kernel terminal line discipline is not active
  • \x03 is passed as raw data to the shell's stdin, not converted to SIGINT
  • \x1a (Ctrl+Z) does not generate SIGTSTP
  • \x04 (Ctrl+D) may not signal EOF correctly
  • Programs checking isatty() get false, disabling interactive features (colors, prompts, line editing)

Suggested Fix

The shell server should allocate a PTY pair using openpty()/forkpty():

  1. Create a PTY master/slave pair
  2. Fork the shell process with the slave as its controlling terminal
  3. Bridge the vsock connection to the PTY master
  4. Forward terminal window size changes if the client sends resize events

This enables the kernel's terminal line discipline which handles:

  • Signal generation (Ctrl+C -> SIGINT, Ctrl+Z -> SIGTSTP)
  • Line editing (backspace, arrow keys via readline)
  • isatty() returns true
  • Proper EOF handling

Affected Components

  • chvm-init shell server (vsock port 1025)
  • chvm-init exec server (vsock port 1024) — may have the same issue
  • chvm attach command
  • Hero Compute web console (connects via vsock port 1025)

Environment

  • my_hypervisor: v0.1.2
  • Guest init: chvm-init (built from same repo)
  • Host OS: Ubuntu 24.04 LTS
  • Cloud Hypervisor: v43.0.0
### Description The shell server in my_hypervisor-init listening on vsock port 1025 runs an interactive shell without allocating a pseudo-terminal (PTY). This breaks interactive terminal behavior when connecting via the web console or my_hypervisor attach. ### Steps to Reproduce **Option A: Via Hero Compute web console** 1. Install and start Hero Compute on a bare-metal server with KVM: ``` git clone https://forge.ourworld.tf/lhumina_code/hero_compute.git cd hero_compute make configure make start ``` 2. Open the dashboard at http://<server-ip>:9001 3. Set a secret in Settings 4. Go to My VMs and click Deploy VM 5. Select an image (e.g., ubuntu-24.04:latest), name the VM, choose 1 slice, and deploy 6. Wait for deployment to complete (VM state shows "running") 7. Click the Console button (terminal icon) on the VM row 8. The web terminal opens and shows a shell prompt 9. Type ping 1.1.1.1 and press Enter 10. Wait for a few ping responses to appear 11. Press Ctrl+C to interrupt the ping **Option B: Via chvm CLI** 1. Create and start a VM: ``` my_hypervisor run --name test-pty --cpus 1 --memory 512 --detach forge.ourworld.tf/lhumina_code/hero_compute_registry/ubuntu-24.04:latest ``` 2. Attach to the VM shell: my_hypervisor attach test-pty 3. Type ping 1.1.1.1 and press Enter 4. Wait for a few ping responses to appear 5. Press Ctrl+C to interrupt the ping **Option C: Via raw vsock connection** 1. Create and start a VM as in Option B 2. Connect directly to the shell server via the vsock socket: echo "CONNECT 1025" | socat - UNIX-CONNECT:~/.chvm/vms/<container-id>/vsock.sock 3. Type ping 1.1.1.1 and press Enter 4. Press Ctrl+C Actual Result ping 1.1.1.1 PING 1.1.1.1 (1.1.1.1) 56(84) bytes of data. 64 bytes from 1.1.1.1: icmp_seq=1 ttl=56 time=5.49 ms 64 bytes from 1.1.1.1: icmp_seq=2 ttl=56 time=5.60 ms ^C^C64 bytes from 1.1.1.1: icmp_seq=3 ttl=56 time=5.53 ms ^C^C^C^C^C64 bytes from 1.1.1.1: icmp_seq=4 ttl=56 time=5.51 ms ^C^C^C64 bytes from 1.1.1.1: icmp_seq=5 ttl=56 time=5.57 ms - The \x03 byte (Ctrl+C) is echoed as ^C in the output - The ping process continues running and is not interrupted - The user cannot stop any foreground process with Ctrl+C - The only way to stop ping is to close the terminal/connection entirely Expected Result - Pressing Ctrl+C once should immediately interrupt the ping process - Ping should display its statistics summary and return to the shell prompt: 64 bytes from 1.1.1.1: icmp_seq=2 ttl=56 time=5.60 ms ^C --- 1.1.1.1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 5.490/5.545/5.600/0.055 ms root@vm:~# Root Cause The shell server spawns a shell process (/bin/bash or /bin/sh) with raw stdin/stdout pipes but no PTY. Without a PTY: - The kernel terminal line discipline is not active - \x03 is passed as raw data to the shell's stdin, not converted to SIGINT - \x1a (Ctrl+Z) does not generate SIGTSTP - \x04 (Ctrl+D) may not signal EOF correctly - Programs checking isatty() get false, disabling interactive features (colors, prompts, line editing) Suggested Fix The shell server should allocate a PTY pair using openpty()/forkpty(): 1. Create a PTY master/slave pair 2. Fork the shell process with the slave as its controlling terminal 3. Bridge the vsock connection to the PTY master 4. Forward terminal window size changes if the client sends resize events This enables the kernel's terminal line discipline which handles: - Signal generation (Ctrl+C -> SIGINT, Ctrl+Z -> SIGTSTP) - Line editing (backspace, arrow keys via readline) - isatty() returns true - Proper EOF handling Affected Components - chvm-init shell server (vsock port 1025) - chvm-init exec server (vsock port 1024) — may have the same issue - chvm attach command - Hero Compute web console (connects via vsock port 1025) Environment - my_hypervisor: v0.1.2 - Guest init: chvm-init (built from same repo) - Host OS: Ubuntu 24.04 LTS - Cloud Hypervisor: v43.0.0
mahmoud changed title from bug: Shell server (vsock port 1025) does not allocate PTY interactive signal handling broken to bug: Shell server (vsock) does not allocate PTY interactive signal handling broken 2026-03-25 22:53:37 +00:00
rawan self-assigned this 2026-03-26 12:39:37 +00:00
Member

Implementation Spec for Issue #70: PTY Controlling Terminal for Shell Server

Objective

Fix the shell server (vsock port 1025) so the spawned shell has a proper controlling terminal, enabling interactive signal handling (Ctrl+C → SIGINT, Ctrl+Z → SIGTSTP, Ctrl+D → EOF).

Note: Terminal window size (resize) forwarding was split into a separate issue: #72

Background Analysis

The shell handler at crates/my_hypervisor-init/src/shell_handler.rs already allocates a PTY pair via open_pty(). However, it has two critical defects:

  1. No setsid() before spawning the shell. The shell process inherits the session of the init process. Without a new session, the PTY slave cannot become the controlling terminal.
  2. No TIOCSCTTY ioctl. The slave was opened with O_NOCTTY, preventing it from becoming the controlling terminal. An explicit TIOCSCTTY call is needed.

Requirements

  • Shell process must run in its own session (setsid()) with the PTY slave as its controlling terminal (TIOCSCTTY)
  • Ctrl+C (0x03) must generate SIGINT to the shell’s foreground process group
  • Ctrl+Z (0x1a) must generate SIGTSTP
  • Ctrl+D (0x04) must signal EOF
  • Programs calling isatty() must get true
  • Ctrl+P, Ctrl+Q detach sequence must continue to work
  • No new crate dependencies

File Modified

File Change
crates/my_hypervisor-init/src/shell_handler.rs Add setsid() + TIOCSCTTY in pre_exec; remove O_NOCTTY from slave open

Implementation

  1. Add use std::os::unix::io::AsRawFd and use std::os::unix::process::CommandExt imports
  2. Extract slave_raw_fd via AsRawFd before cloning the slave for stdin/stdout/stderr
  3. Add unsafe pre_exec closure on the Command that:
    • Calls libc::setsid() to create a new session (makes bash the session leader)
    • Calls libc::ioctl(slave_raw_fd, libc::TIOCSCTTY, 0) to set the PTY slave as the controlling terminal
  4. Remove O_NOCTTY from the slave open() call in open_pty()

Acceptance Criteria

  • chvm attach <vm> connects to a shell where tty prints /dev/pts/N
  • Ctrl+C sends SIGINT (e.g., ping 1.1.1.1 is interrupted by Ctrl+C)
  • Ctrl+Z sends SIGTSTP (e.g., cat is suspended)
  • Ctrl+D sends EOF (exits shell at empty prompt)
  • isatty() returns true (e.g., ls shows colors, python3 shows interactive prompt)
  • Ctrl+P, Ctrl+Q detach sequence continues to work
  • chvm exec <vm> -- ls -la (non-interactive) continues to work
  • cargo test --workspace passes
  • cargo clippy -- -D warnings passes
## Implementation Spec for Issue #70: PTY Controlling Terminal for Shell Server ### Objective Fix the shell server (vsock port 1025) so the spawned shell has a proper controlling terminal, enabling interactive signal handling (Ctrl+C → SIGINT, Ctrl+Z → SIGTSTP, Ctrl+D → EOF). > **Note:** Terminal window size (resize) forwarding was split into a separate issue: #72 ### Background Analysis The shell handler at `crates/my_hypervisor-init/src/shell_handler.rs` already allocates a PTY pair via `open_pty()`. However, it has two critical defects: 1. **No `setsid()` before spawning the shell.** The shell process inherits the session of the init process. Without a new session, the PTY slave cannot become the controlling terminal. 2. **No `TIOCSCTTY` ioctl.** The slave was opened with `O_NOCTTY`, preventing it from becoming the controlling terminal. An explicit `TIOCSCTTY` call is needed. ### Requirements - Shell process must run in its own session (`setsid()`) with the PTY slave as its controlling terminal (`TIOCSCTTY`) - Ctrl+C (0x03) must generate SIGINT to the shell’s foreground process group - Ctrl+Z (0x1a) must generate SIGTSTP - Ctrl+D (0x04) must signal EOF - Programs calling `isatty()` must get `true` - Ctrl+P, Ctrl+Q detach sequence must continue to work - No new crate dependencies ### File Modified | File | Change | |---|---| | `crates/my_hypervisor-init/src/shell_handler.rs` | Add `setsid()` + `TIOCSCTTY` in `pre_exec`; remove `O_NOCTTY` from slave open | ### Implementation 1. Add `use std::os::unix::io::AsRawFd` and `use std::os::unix::process::CommandExt` imports 2. Extract `slave_raw_fd` via `AsRawFd` before cloning the slave for stdin/stdout/stderr 3. Add `unsafe pre_exec` closure on the Command that: - Calls `libc::setsid()` to create a new session (makes bash the session leader) - Calls `libc::ioctl(slave_raw_fd, libc::TIOCSCTTY, 0)` to set the PTY slave as the controlling terminal 4. Remove `O_NOCTTY` from the slave `open()` call in `open_pty()` ### Acceptance Criteria - [ ] `chvm attach <vm>` connects to a shell where `tty` prints `/dev/pts/N` - [ ] Ctrl+C sends SIGINT (e.g., `ping 1.1.1.1` is interrupted by Ctrl+C) - [ ] Ctrl+Z sends SIGTSTP (e.g., `cat` is suspended) - [ ] Ctrl+D sends EOF (exits shell at empty prompt) - [ ] `isatty()` returns true (e.g., `ls` shows colors, `python3` shows interactive prompt) - [ ] Ctrl+P, Ctrl+Q detach sequence continues to work - [ ] `chvm exec <vm> -- ls -la` (non-interactive) continues to work - [ ] `cargo test --workspace` passes - [ ] `cargo clippy -- -D warnings` passes
rawan added this to the ACTIVE project 2026-03-26 13:26:56 +00:00
Member

Test Results

Branch: development
Commit: 243e61f
Date: 2026-03-26

Clippy

  • Status: Passed
  • Mode: --workspace -- -D warnings (strict, warnings are errors)
  • No warnings or errors detected.

Unit Tests

  • Status: Passed
  • Total: 186 tests
  • Passed: 186
  • Failed: 0

Breakdown by crate:

Crate Tests Result
my_hypervisor-cli 17 All passed
my_hypervisor-init 6 All passed
my_hypervisor-lib 163 All passed
Doc-tests 0 N/A
## Test Results **Branch:** `development` **Commit:** `243e61f` **Date:** 2026-03-26 ### Clippy - Status: ✅ Passed - Mode: `--workspace -- -D warnings` (strict, warnings are errors) - No warnings or errors detected. ### Unit Tests - Status: ✅ Passed - **Total: 186 tests** - **Passed: 186** - **Failed: 0** #### Breakdown by crate: | Crate | Tests | Result | |-------|-------|--------| | `my_hypervisor-cli` | 17 | ✅ All passed | | `my_hypervisor-init` | 6 | ✅ All passed | | `my_hypervisor-lib` | 163 | ✅ All passed | | Doc-tests | 0 | ✅ N/A |
Member

Implementation Summary

Changes Made

1 file modified:

crates/my_hypervisor-init/src/shell_handler.rs

  • Added setsid() call in child process pre_exec to create a new session, detaching from init's session
  • Added TIOCSCTTY ioctl to make the PTY slave the controlling terminal
  • Removed O_NOCTTY flag from slave PTY open call

What This Fixes

  • Ctrl+C now sends SIGINT to the foreground process group (e.g., interrupts ping)
  • Ctrl+Z now sends SIGTSTP (suspends foreground process)
  • Ctrl+D now signals EOF correctly
  • isatty() returns true (enables colors, interactive prompts, line editing)
  • tty command prints /dev/pts/N instead of "not a tty"

Note: Terminal window size (resize) forwarding was split into a separate issue: #72

Test Results

  • Clippy (strict): 0 warnings
  • Unit tests: 186/186 passed
## Implementation Summary ### Changes Made **1 file modified:** #### `crates/my_hypervisor-init/src/shell_handler.rs` - Added `setsid()` call in child process `pre_exec` to create a new session, detaching from init's session - Added `TIOCSCTTY` ioctl to make the PTY slave the controlling terminal - Removed `O_NOCTTY` flag from slave PTY open call ### What This Fixes - **Ctrl+C** now sends SIGINT to the foreground process group (e.g., interrupts `ping`) - **Ctrl+Z** now sends SIGTSTP (suspends foreground process) - **Ctrl+D** now signals EOF correctly - **`isatty()`** returns true (enables colors, interactive prompts, line editing) - **`tty`** command prints `/dev/pts/N` instead of "not a tty" > **Note:** Terminal window size (resize) forwarding was split into a separate issue: #72 ### Test Results - Clippy (strict): ✅ 0 warnings - Unit tests: ✅ 186/186 passed
rawan closed this issue 2026-03-30 09:50:41 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
geomind_code/my_hypervisor#70
No description provided.