Skip to content

UB in accept

The following code will give a page fault inside listener.accept() when you connect through telnet from another machine:

use std::env;
use std::fs::{File, OpenOptions};
use std::io::{self, Result, Write};
use std::net::{TcpListener, TcpStream};
use std::os::fd::AsRawFd;
use std::os::unix::io::{FromRawFd, IntoRawFd, RawFd};
use std::os::unix::process::CommandExt;
use std::process::{Child, Command, Stdio};
use std::sync::Mutex;

#[cfg(target_os = "redox")]
use redox_termios::Winsize;

use getpty::getpty;

mod getpty;

#[cfg(not(target_os = "redox"))]
pub fn pre_exec() -> Result<()> {
    use libc;
    unsafe {
        libc::setsid();
        libc::ioctl(0, libc::TIOCSCTTY, 1);
    }
    Ok(())
}

#[cfg(target_os = "redox")]
pub fn pre_exec() -> Result<()> {
    Ok(())
}

fn handle(stream: TcpStream, master_fd: RawFd, process: Child) {
    #[cfg(not(target_os = "redox"))]
    unsafe {
        let size = libc::winsize {
            ws_row: 30,
            ws_col: 80,
            ws_xpixel: 0,
            ws_ypixel: 0,
        };
        libc::ioctl(master_fd, libc::TIOCSWINSZ, &size as *const libc::winsize);
    }
    #[cfg(target_os = "redox")]
    {
        let winsize =
            syscall::dup(master_fd as usize, b"winsize").expect("failed to get winsize property");
        let size = Winsize {
            ws_row: 30,
            ws_col: 80,
        };
        let ret = syscall::write(winsize, &size);
        syscall::close(winsize).expect("failed to close winsize property");
        ret.expect("failed to set winsize property");
    }

    let master = unsafe { File::from_raw_fd(master_fd) };

    let process = Mutex::new(process);
    std::thread::scope(|scope| {
        scope.spawn(|| {
            if let Err(err) = std::io::copy(&mut &stream, &mut &master) {
                eprintln!("error copying streams: {err}");
                process
                    .lock()
                    .unwrap()
                    .kill()
                    .expect("failed to kill child process");
                process
                    .lock()
                    .unwrap()
                    .wait()
                    .expect("failed to wait for child process");
                std::process::exit(2);
            }
        });
        if let Err(err) = std::io::copy(&mut &master, &mut &stream) {
            eprintln!("error copying streams: {err}");
            process
                .lock()
                .unwrap()
                .kill()
                .expect("failed to kill child process");
            process
                .lock()
                .unwrap()
                .wait()
                .expect("failed to wait for child process");
            std::process::exit(3);
        }
    });

    process
        .lock()
        .unwrap()
        .kill()
        .expect("failed to kill child process");
    process
        .lock()
        .unwrap()
        .wait()
        .expect("failed to wait for child process");
}

fn telnet() {
    let listener = TcpListener::bind("0.0.0.0:23").unwrap();

    loop {
        eprintln!("listening");
        // eprintln!("{}", unsafe {
        //     libc::accept(listener.as_raw_fd(), core::ptr::null_mut(), core::ptr::null_mut())
        // });
        let stream = match listener.accept() {
            Ok((stream, _)) => stream,
            Err(err) => {
                eprintln!("accept error: {}", err);
                continue;
            }
        };
        eprintln!("accepted stream");

        //if unsafe { libc::fork() as usize } == 0 {
        //drop(listener);

        eprintln!("creating pty");
        let (master_fd, tty_path) = getpty();
        eprintln!("created pty");

        let slave_stdin = OpenOptions::new()
            .read(true)
            .write(true)
            .open(&tty_path)
            .unwrap();
        let slave_stdout = OpenOptions::new()
            .read(true)
            .write(true)
            .open(&tty_path)
            .unwrap();
        let slave_stderr = OpenOptions::new()
            .read(true)
            .write(true)
            .open(&tty_path)
            .unwrap();

        eprintln!("opened pty");

        match unsafe {
            Command::new("login")
                .env("COLUMNS", "80")
                .env("LINES", "30")
                .env("TERM", "linux")
                .env("TTY", tty_path)
                .stdin(Stdio::from_raw_fd(slave_stdin.into_raw_fd()))
                .stdout(Stdio::from_raw_fd(slave_stdout.into_raw_fd()))
                .stderr(Stdio::from_raw_fd(slave_stderr.into_raw_fd()))
                .pre_exec(|| pre_exec())
                .spawn()
        } {
            Ok(process) => {
                eprintln!("spawned login");
                handle(stream, master_fd, process);
            }
            Err(err) => {
                let _ = writeln!(io::stderr().lock(), "failed to execute 'login': {err}");
            }
        }

        //    std::process::exit(0);
        //}
    }
}

fn main() {
    let mut background = false;
    for arg in env::args().skip(1) {
        match arg.as_ref() {
            "-b" => background = true,
            _ => (),
        }
    }

    println!("Telnet");
    if background {
        if unsafe { libc::fork() as usize } == 0 {
            telnet();
        }
    } else {
        telnet();
        panic!();
    }
}