File: demonstrate_fd_reuse_bug.rs

package info (click to toggle)
rust-fork 0.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 376 kB
  • sloc: makefile: 2
file content (147 lines) | stat: -rw-r--r-- 6,683 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#![allow(clippy::expect_used)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::panic)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::match_wild_err_arm)]
#![allow(clippy::cast_ptr_alignment)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::ptr_as_ptr)]

/// Educational demonstration of the file descriptor reuse bug
///
/// This example shows:
/// 1. The BUG: How close_fd() causes fd reuse
/// 2. The FIX: How redirect_stdio() prevents fd reuse
///
/// Run with: cargo run --example demonstrate_fd_reuse_bug
use std::{fs::File, io::Write, os::unix::io::AsRawFd, process::exit};

use fork::{Fork, close_fd, fork, redirect_stdio, waitpid};

fn main() {
    println!("\n╔═══════════════════════════════════════════════════════════════╗");
    println!("║     DEMONSTRATION: File Descriptor Reuse Bug                 ║");
    println!("╚═══════════════════════════════════════════════════════════════╝\n");

    demonstrate_bug();
    demonstrate_fix();

    println!("\n╔═══════════════════════════════════════════════════════════════╗");
    println!("║                      SUMMARY                                  ║");
    println!("╚═══════════════════════════════════════════════════════════════╝");
    println!("\nBUG: close_fd() frees fd 0,1,2 → files reuse them → corruption!");
    println!("FIX: redirect_stdio() keeps fd 0,1,2 busy → files get fd >= 3\n");
}

fn demonstrate_bug() {
    println!("═══════════════════════════════════════════════════════════════");
    println!("PART 1: THE BUG (using close_fd)");
    println!("═══════════════════════════════════════════════════════════════\n");

    match fork() {
        Ok(Fork::Parent(child)) => {
            waitpid(child).unwrap();

            // Read what the child wrote
            let content = std::fs::read_to_string("/tmp/demo_bug.txt").unwrap_or_default();

            println!("\nResult with close_fd():");
            println!("  File content: '{}'", content.trim());

            if content.contains("This is debug output") {
                println!("  ⚠️  BUG DETECTED: Debug output corrupted the file!");
            } else {
                println!("  File only contains: {}", content.trim());
            }

            // Cleanup
            let _ = std::fs::remove_file("/tmp/demo_bug.txt");
        }
        Ok(Fork::Child) => {
            // Close stdio - THIS CAUSES THE BUG
            close_fd().unwrap();

            // Open a file - it will get a low fd (0, 1, or 2)
            let mut file = File::create("/tmp/demo_bug.txt").unwrap();
            let fd = file.as_raw_fd();

            println!("After close_fd():");
            println!("  File got fd = {}", fd);

            if fd < 3 {
                println!("  ⚠️  WARNING: File got fd < 3!");
                println!("  Any println! or panic will write to this file!\n");

                // Simulate what might happen with debug output
                // We'll write directly to fd=2 (stderr) to show the problem
                let debug_msg = b"This is debug output that should NOT be in the file!\n";
                unsafe {
                    // If another file got fd=2, this write goes to that file!
                    libc::write(2, debug_msg.as_ptr() as *const _, debug_msg.len());
                }
            }

            // Write intended data
            file.write_all(b"Expected data\n").unwrap();
            exit(0);
        }
        Err(_) => panic!("Fork failed"),
    }
}

fn demonstrate_fix() {
    println!("\n═══════════════════════════════════════════════════════════════");
    println!("PART 2: THE FIX (using redirect_stdio)");
    println!("═══════════════════════════════════════════════════════════════\n");

    match fork() {
        Ok(Fork::Parent(child)) => {
            waitpid(child).unwrap();

            // Read what the child wrote
            let content = std::fs::read_to_string("/tmp/demo_fix.txt").unwrap_or_default();

            println!("\nResult with redirect_stdio():");
            println!("  File content: '{}'", content.trim());

            if content.contains("debug output") {
                println!("  ❌ UNEXPECTED: Debug leaked to file");
            } else {
                println!("  ✅ SUCCESS: File only contains intended data!");
                println!("  Debug output went to /dev/null (discarded safely)");
            }

            // Cleanup
            let _ = std::fs::remove_file("/tmp/demo_fix.txt");
        }
        Ok(Fork::Child) => {
            // Redirect stdio to /dev/null - THIS FIXES THE BUG
            redirect_stdio().unwrap();

            // Open a file - it will get fd >= 3
            let mut file = File::create("/tmp/demo_fix.txt").unwrap();
            let fd = file.as_raw_fd();

            println!("After redirect_stdio():");
            println!("  File got fd = {}", fd);
            println!("  fd 0,1,2 are now occupied by /dev/null");

            if fd >= 3 {
                println!("  ✅ GOOD: File got fd >= 3");
                println!("  Any println! or panic goes to /dev/null (safe)\n");

                // Try to write debug output - it goes to /dev/null!
                let debug_msg = b"This is debug output that goes to /dev/null\n";
                unsafe {
                    // This write goes to /dev/null, not to our file!
                    libc::write(2, debug_msg.as_ptr() as *const _, debug_msg.len());
                }
            }

            // Write intended data
            file.write_all(b"Expected data\n").unwrap();
            exit(0);
        }
        Err(_) => panic!("Fork failed"),
    }
}