Skip to main content
explanation
Source Link
madlaina
  • 751
  • 5
  • 11

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! (or std::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[..]);
}}

Try it online!

Rust, score 3

Rust version: rustc 1.45.1 (c367798cf 2020-07-26)

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! (or std::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[..]);
}}

Try it online!

Source Link
madlaina
  • 751
  • 5
  • 11

Rust, score 3

pwx

No extern possible this time, so no calling C functions! (or other crates, but that wouldn't be allowed anyways)

Pretty challenging to output to stdout without being able to type print or write!

Rust version: rustc 1.45.1 (c367798cf 2020-07-26)