-
Notifications
You must be signed in to change notification settings - Fork 280
Description
This bug arises when using a compound literal to initialize a struct containing a pthread_rwlock_t, and passing a pointer to that struct to a thread. While this is safe and valid in C, the Rust code generated by C2Rust mishandles the struct's memory lifetime or layout, resulting in corrupted field values at runtime.
This issue is distinct from [#1216], which involves PTHREAD_MUTEX_INITIALIZER and leads to a runtime crash. In contrast, this bug causes silent data corruption without triggering a panic or assertion failure.
Source C code:
#include <pthread.h>
#include <stdio.h>
typedef struct {
pthread_rwlock_t lock;
int val;
} S;
void *reader(void *arg) {
S *s = arg;
pthread_rwlock_rdlock(&s->lock);
printf("val: %d\n", s->val);
pthread_rwlock_unlock(&s->lock);
return NULL;
}
int main() {
pthread_t t;
S *s = (S[]){ { PTHREAD_RWLOCK_INITIALIZER, 42 } };
pthread_create(&t, NULL, reader, s);
pthread_join(t, NULL);
return 0;
}C Output:
val: 42
Click to view translated Rust code
#![allow(
dead_code,
mutable_transmutes,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
unused_assignments,
unused_mut
)]
use ::transpiled_code::*;
extern "C" {
fn pthread_create(
__newthread: *mut pthread_t,
__attr: *const pthread_attr_t,
__start_routine: Option::<
unsafe extern "C" fn(*mut libc::c_void) -> *mut libc::c_void,
>,
__arg: *mut libc::c_void,
) -> libc::c_int;
fn pthread_join(
__th: pthread_t,
__thread_return: *mut *mut libc::c_void,
) -> libc::c_int;
fn pthread_rwlock_rdlock(__rwlock: *mut pthread_rwlock_t) -> libc::c_int;
fn pthread_rwlock_unlock(__rwlock: *mut pthread_rwlock_t) -> libc::c_int;
fn printf(_: *const libc::c_char, _: ...) -> libc::c_int;
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct __pthread_rwlock_arch_t {
pub __readers: libc::c_uint,
pub __writers: libc::c_uint,
pub __wrphase_futex: libc::c_uint,
pub __writers_futex: libc::c_uint,
pub __pad3: libc::c_uint,
pub __pad4: libc::c_uint,
pub __cur_writer: libc::c_int,
pub __shared: libc::c_int,
pub __rwelision: libc::c_schar,
pub __pad1: [libc::c_uchar; 7],
pub __pad2: libc::c_ulong,
pub __flags: libc::c_uint,
}
pub type pthread_t = libc::c_ulong;
#[derive(Copy, Clone)]
#[repr(C)]
pub union pthread_attr_t {
pub __size: [libc::c_char; 56],
pub __align: libc::c_long,
}
#[derive(Copy, Clone)]
#[repr(C)]
pub union pthread_rwlock_t {
pub __data: __pthread_rwlock_arch_t,
pub __size: [libc::c_char; 56],
pub __align: libc::c_long,
}
pub type C2RustUnnamed = libc::c_uint;
pub const PTHREAD_RWLOCK_DEFAULT_NP: C2RustUnnamed = 0;
pub const PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP: C2RustUnnamed = 2;
pub const PTHREAD_RWLOCK_PREFER_WRITER_NP: C2RustUnnamed = 1;
pub const PTHREAD_RWLOCK_PREFER_READER_NP: C2RustUnnamed = 0;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct S {
pub lock: pthread_rwlock_t,
pub val: libc::c_int,
}
#[no_mangle]
pub unsafe extern "C" fn reader(mut arg: *mut libc::c_void) -> *mut libc::c_void {
let mut s: *mut S = arg as *mut S;
pthread_rwlock_rdlock(&mut (*s).lock);
printf(b"val: %d\n\0" as *const u8 as *const libc::c_char, (*s).val);
pthread_rwlock_unlock(&mut (*s).lock);
return 0 as *mut libc::c_void;
}
unsafe fn main_0() -> libc::c_int {
let mut t: pthread_t = 0;
let mut s: *mut S = [
{
let mut init = S {
lock: pthread_rwlock_t {
__data: {
let mut init = __pthread_rwlock_arch_t {
__readers: 0 as libc::c_int as libc::c_uint,
__writers: 0 as libc::c_int as libc::c_uint,
__wrphase_futex: 0 as libc::c_int as libc::c_uint,
__writers_futex: 0 as libc::c_int as libc::c_uint,
__pad3: 0 as libc::c_int as libc::c_uint,
__pad4: 0 as libc::c_int as libc::c_uint,
__cur_writer: 0 as libc::c_int,
__shared: 0 as libc::c_int,
__rwelision: 0 as libc::c_int as libc::c_schar,
__pad1: [
0 as libc::c_int as libc::c_uchar,
0 as libc::c_int as libc::c_uchar,
0 as libc::c_int as libc::c_uchar,
0 as libc::c_int as libc::c_uchar,
0 as libc::c_int as libc::c_uchar,
0 as libc::c_int as libc::c_uchar,
0 as libc::c_int as libc::c_uchar,
],
__pad2: 0 as libc::c_int as libc::c_ulong,
__flags: PTHREAD_RWLOCK_DEFAULT_NP as libc::c_int
as libc::c_uint,
};
init
},
},
val: 42 as libc::c_int,
};
init
},
]
.as_mut_ptr();
pthread_create(
&mut t,
0 as *const pthread_attr_t,
Some(reader as unsafe extern "C" fn(*mut libc::c_void) -> *mut libc::c_void),
s as *mut libc::c_void,
);
pthread_join(t, 0 as *mut *mut libc::c_void);
return 0 as libc::c_int;
}
pub fn main() {
unsafe { ::std::process::exit(main_0() as i32) }
}Translated Rust Output:
Rust code prints incorrect value (e.g., val: 0) or worse — leads to undefined behavior.
val: 0
Root Cause
C2Rust does not promote the temporary to a valid heap-allocated or static location. As a result, the memory becomes invalid by the time the thread accesses it.