#ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include "mcpu-asm.h" #define CHECK(cond,msg) \ do { \ if( !(cond) ) \ { \ fprintf( stderr, "FAIL: %s\n", (msg) ); \ return( 1 ); \ } \ } while( 0 ) static void put32( __mpu_uint32_t address, __mpu_uint32_t value ) { __cpu_data_put_u32( __cpu_reg_ptr( 32, address ), value ); } static void put64( __mpu_uint32_t address, __mpu_uint64_t value ) { __cpu_data_put_u64( __cpu_reg_ptr( 64, address ), value ); } static __mpu_uint64_t reg64( __mpu_uint32_t address ) { return( __cpu_data_get_u64( __cpu_reg_ptr( 64, address ) ) ); } static int run_syscall( __mpu_uint32_t nargs, __mpu_uint32_t sysno, __mpu_uint64_t arg0, __mpu_uint64_t arg1, __mpu_uint64_t arg2, __mpu_uint64_t *ret ) { int rc; put32( CPU_SYSCALL_NARGS_ADDR, nargs ); put32( CPU_SYSCALL_SYSNO_ADDR, sysno ); put64( CPU_SYSCALL_ARG0_ADDR, arg0 ); put64( CPU_SYSCALL_ARG1_ADDR, arg1 ); put64( CPU_SYSCALL_ARG2_ADDR, arg2 ); __cpu_pc = 0; rc = __cpu_run(); if( rc != CPU_EXEC_SUCCESS ) return( rc ); *ret = reg64( CPU_SYSCALL_RET_ADDR ); return( 0 ); } /* End of run_syscall() */ static __mpu_int64_t signed_result( __mpu_uint64_t value ) { __mpu_int64_t ret; memcpy( &ret, &value, sizeof( ret ) ); return( ret ); } /* End of signed_result() */ int main( void ) { static const char source[] = ".text\n" "SYSCALL\n" "HALT\n"; cpu_asm_image image; char err[256]; __mpu_uint64_t ret; memset( &image, 0, sizeof( image ) ); CHECK( __NR_dup == 0x126, "public __NR_dup ABI number" ); CHECK( __NR_dup2 == 0x127, "public __NR_dup2 ABI number" ); __cpu_init(); CHECK( cpu_assemble_text( source, &image, err, sizeof( err ) ) == 0, err ); CHECK( __cpu_program_set( image.code, image.code_size ) == 0, "program load" ); /* dup() returns the lowest free guest descriptor. */ CHECK( run_syscall( 1, __NR_dup, 1, 0, 0, &ret ) == 0, "dup stdout" ); CHECK( ret == 3, "dup returns lowest free guest fd 3" ); /* Descriptor flags are not copied by dup(). */ CHECK( run_syscall( 3, __NR_fcntl, 1, F_SETFD, FD_CLOEXEC, &ret ) == 0, "set FD_CLOEXEC on source" ); CHECK( ret == 0, "F_SETFD source result" ); CHECK( run_syscall( 3, __NR_fcntl, 3, F_GETFD, 0, &ret ) == 0, "F_GETFD duplicated descriptor" ); CHECK( ret == 0, "dup clears descriptor flags" ); /* dup2() installs exactly the requested guest descriptor. */ CHECK( run_syscall( 2, __NR_dup2, 1, 10, 0, &ret ) == 0, "dup2 stdout to guest fd 10" ); CHECK( ret == 10, "dup2 returns requested guest fd" ); CHECK( run_syscall( 3, __NR_fcntl, 10, F_SETFD, FD_CLOEXEC, &ret ) == 0, "set FD_CLOEXEC on dup2 target" ); CHECK( ret == 0, "set target flags" ); /* Replacing an occupied target clears descriptor flags. */ CHECK( run_syscall( 2, __NR_dup2, 3, 10, 0, &ret ) == 0, "dup2 replaces occupied target" ); CHECK( ret == 10, "dup2 replacement return" ); CHECK( run_syscall( 3, __NR_fcntl, 10, F_GETFD, 0, &ret ) == 0, "F_GETFD replaced target" ); CHECK( ret == 0, "dup2 replacement clears descriptor flags" ); /* oldfd == newfd is a no-op and preserves descriptor flags. */ CHECK( run_syscall( 3, __NR_fcntl, 1, F_GETFD, 0, &ret ) == 0, "F_GETFD source before dup2 self" ); CHECK( ret == FD_CLOEXEC, "source flag before dup2 self" ); CHECK( run_syscall( 2, __NR_dup2, 1, 1, 0, &ret ) == 0, "dup2 oldfd equals newfd" ); CHECK( ret == 1, "dup2 self return" ); CHECK( run_syscall( 3, __NR_fcntl, 1, F_GETFD, 0, &ret ) == 0, "F_GETFD source after dup2 self" ); CHECK( ret == FD_CLOEXEC, "dup2 self preserves descriptor flags" ); /* Closing a borrowed standard guest fd does not close HOST stdio, and dup() may subsequently reuse that lowest free guest slot. */ CHECK( run_syscall( 1, __NR_close, 0, 0, 0, &ret ) == 0, "close borrowed guest stdin" ); CHECK( ret == 0, "close borrowed guest stdin result" ); CHECK( run_syscall( 1, __NR_dup, 1, 0, 0, &ret ) == 0, "dup reuses closed guest fd 0" ); CHECK( ret == 0, "dup returns lowest free guest fd 0" ); /* Invalid source and target descriptors use the stable MCPU EBADF value. */ CHECK( run_syscall( 1, __NR_dup, 255, 0, 0, &ret ) == 0, "dup invalid source" ); CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, "dup invalid source returns -EBADF" ); CHECK( run_syscall( 2, __NR_dup2, 1, 256, 0, &ret ) == 0, "dup2 invalid target" ); CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, "dup2 invalid target returns -EBADF" ); CHECK( run_syscall( 3, __NR_fcntl, 10, F_SETFD, FD_CLOEXEC, &ret ) == 0, "set target flag before invalid-source dup2" ); CHECK( run_syscall( 2, __NR_dup2, 255, 10, 0, &ret ) == 0, "dup2 invalid source with occupied target" ); CHECK( signed_result( ret ) == -(__mpu_int64_t)EBADF, "dup2 invalid source returns -EBADF" ); CHECK( run_syscall( 3, __NR_fcntl, 10, F_GETFD, 0, &ret ) == 0, "target survives invalid-source dup2" ); CHECK( ret == FD_CLOEXEC, "invalid-source dup2 leaves target unchanged" ); CHECK( run_syscall( 1, __NR_close, 0, 0, 0, &ret ) == 0, "close reused guest fd 0" ); CHECK( run_syscall( 1, __NR_close, 3, 0, 0, &ret ) == 0, "close dup guest fd 3" ); CHECK( run_syscall( 1, __NR_close, 10, 0, 0, &ret ) == 0, "close dup2 guest fd 10" ); cpu_asm_image_free( &image ); __cpu_free_context(); puts( "libmcpu dup/dup2 regression: PASS" ); return( 0 ); } /* End of main() */