Program goes into infinite loop without any explicit loop


#1

Hello,

While exploring rust I wrote a program for reading and writing struct to and from a file , but when I run it from command line it goes into infinite loop.

Program flow is that it first take ref of a given struct and then write to a file and next take a mut ref to a struct and read from a file . Some where some thing goes wrong and it keep printing a particular println! (…) .

rustc version is rustc 1.0.0-dev (e40449e0d 2015-04-16) (built 2015-04-16)

Here is the github link https://github.com/ajayprataps/rust-examples/blob/master/rw_struct1.rs

Code for ref

/// Write and Read struct from a file
/// rustc rw_struct1.rs

use std::mem;
use std::slice;
use std::convert::{AsRef};

use std::fs::File;
use std::io::{Read,Write};

#[repr(C)]
pub struct XYZ {
  x: [i32;16],
}

/// Goes into infinite loop ?

fn main () {
  let mut xyz: XYZ = XYZ {x: [100; 16]};

  let mut file = File::create ("abc").unwrap();

  println!("({}, {}, {})" ,xyz.x[0],xyz.x[1],xyz.x[2]);
  
  {
    
    let bites: &[u8] = xyz.as_ref () ;

    file.write (bites).unwrap();
  }
  

  xyz.x = [101;16];

  println!("===> ({}, {}, {})" ,xyz.x[0],xyz.x[1],xyz.x[2]);

  // Some where here it goes into loop and keep printing
  // above line
  
  file.flush().unwrap();

  
  {
    
    file = File::open("abc").unwrap ();
  
    let bites: &mut [u8] = xyz.as_mut () ;

    file.read (bites).unwrap();

  }
  
  println!("({}, {}, {})" ,xyz.x[0],xyz.x[1],xyz.x[2]);

}



/// XYZ as &[u8]

impl AsRef<[u8]> for XYZ {
  fn as_ref (&self) -> &[u8] {
    let slice= unsafe {
      let p: *const u8 = mem::transmute (&self);
      slice::from_raw_parts (p, mem::size_of::<XYZ>())
    };

    slice
  }

}


/// XYZ as &mut [u8]
impl AsMut<[u8]> for XYZ {
  fn as_mut (&mut self) -> &mut [u8] {
    let slice= unsafe {
      let p: *mut u8 = mem::transmute (&self);
      slice::from_raw_parts_mut (p, mem::size_of::<XYZ>())
    };

    slice
  }

}

#2

It looks like the call to File::read is gotoing back to the call to File::write somehow. That’s wierd as hell. Maybe file a bug report about it.


#3

On Mon, Apr 27, 2015 at 1:37 AM, ajay users@rust-lang.org wrote:


#4

Ok it is fixed now . Only change in two places

let p: *const u8 = mem::transmute (self);

and

let p: *mut u8 = mem::transmute (self);

#5

Yep. My previous reply was eaten by Discourse (/sigh/), but it was an attempt to inform you about this issue. It’s pretty funny what effect you got, actually - if I’m right, you successfully exploited your own program via a stack smash.

When self is an &XYZ, &self is an &&XYZ - a pointer to a stack slot in as_ref's frame that points to the object data:

     as_ref's frame                                           main's frame
| self | other stack data | saved regs ... | return address | ...
^-- transmuted reference points to here

You transmuted this pointer to a pointer to a 64-byte object - making it represent a far larger area of the stack than intended, extending at least past the return address and perhaps into main's frame - then returned, invalidating as_ref's frame. Then you called write, creating a new frame at the same boundary:

     write's frame                                            main's frame
| some data from write    | saved regs ... | return address | ...
^-- 

At the point the data was actually copied out to the file, it probably included some random stack data from write, plus saved registers from main and a return address pointing to right after the call to write in the main function. Copying this data did no harm, so write returned successfully and the program kept running, until… you did the same thing again, but with read.

as_mut did the same thing as as_ref, and similarly read was called with a pointer into its own stack frame. This time, rather than being copied into the file, the stack frame was overwritten from the file - the saved registers and return address were replaced with those from the write call, perhaps along with some variables from read. Seems that this wanton variable modification was not enough to make read crash, and while the order and location on the stack of each saved register and the return address depend on the function, they happened to be sufficiently compatible between read and write that read was able to successfully return - to the old return address, just after the call to write! Hence the infinite loop.

Of course, this only works out so neatly by chance; if you enabled optimizations, chances are the program would do something else - perhaps crashing, perhaps exiting successfully because the data overwritten by read wasn’t used.


#6

Thanks for your detail answer. I made the mistake by stupidly destructing the argument &self as & and self and hence &self to transmute . It is good that I made this mistake will remember it for a long time (oh well )

Thanks.