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"),
}
}
|