Rust, score 3 (safe)
Rust version: rustc 1.45.1 (c367798cf 2020-07-26)
Solution
The main challenge is printing to stdout. I identified the following methods:
print!
/println!
std::io::stdout()
and then:write!
/writeln!
(orstd::fmt::Write
)- using the
std::io::Write
trait and calling.write()
or similar
All of these require calling a method or macro whose name is excluded by p
or w
.
Enter trait objects, Rust's method of having runtime polymorphism. Trait objects are pointers both to some data (like regular pointers) and to a vtable
which is used to look up the implementation of the trait method when called. So a the code
let trait_obj : &mut dyn Write = ...;
trait_obj.write(&buf[..]);
is transformed to something like this
let trait_obj : (&WriteVtable, *mut ()) = ...;
(trait_obj.0[WriteVtable::write_index])(trait_obj.1, &buf[..])
Now we obviously still can't directly call .write
on the &dyn Write
trait object, but we can instead do the vtable lookup ourselves. This is extremely unsafe, but it works. Now Rust understandably doesn't provide a way to get the index of a trait method in the vtable (wich we probably couldn't do anyways without spelling write
). This is implementation dependent code, which is why I specified the compiler version.
Looking at the compiler code that generates the vtable, we see that it first contains the Drop::drop
implementation (needed for owned trait object such as Box<dyn Trait>
) and then size and alignment. Then come the trait methods in the order specified by the function vtable_methods
. We see it first collects methods from supertraits, and then methods from the trait in definition order. Looking at the trait definition for std::io::Write
, we see that it has no supertraits, and write
is the first method, so its vtable index is 3.
This is the final code:
use std::io::Write;
fn main() { unsafe {
let y = std::io::stdout();
let lock = y.lock();
let x : &dyn Write = &lock;
let (data,vtable) = std::mem::transmute::<&dyn Write, (*const (), *mut usize)>(x);
let z : usize = vtable.offset(3).read();
let fun = std::mem::transmute::<_, fn (*mut (), &[u8]) -> std::io::Result<usize>>(z);
let array = [112,119,120];
fun(std::mem::transmute(data), &array[..]);
}}