2014-08-07 12:52:50 +05:30
/*
Unix SMB / CIFS implementation .
test suite for SMB2 replay
Copyright ( C ) Anubhav Rakshit 2014
Copyright ( C ) Stefan Metzmacher 2014
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "includes.h"
# include "libcli/smb2/smb2.h"
# include "libcli/smb2/smb2_calls.h"
# include "torture/torture.h"
# include "torture/smb2/proto.h"
# include "../libcli/smb/smbXcli_base.h"
# include "lib/cmdline/popt_common.h"
# include "auth/credentials/credentials.h"
# include "libcli/security/security.h"
# include "libcli/resolve/resolve.h"
# include "lib/param/param.h"
# include "lib/events/events.h"
# define CHECK_VAL(v, correct) do { \
if ( ( v ) ! = ( correct ) ) { \
torture_result ( tctx , TORTURE_FAIL , " (%s): wrong value for %s got 0x%x - should be 0x%x \n " , \
__location__ , # v , ( int ) v , ( int ) correct ) ; \
ret = false ; \
goto done ; \
} } while ( 0 )
# define CHECK_STATUS(status, correct) do { \
if ( ! NT_STATUS_EQUAL ( status , correct ) ) { \
torture_result ( tctx , TORTURE_FAIL , __location__ " : Incorrect status %s - should be %s " , \
nt_errstr ( status ) , nt_errstr ( correct ) ) ; \
ret = false ; \
goto done ; \
} } while ( 0 )
# define CHECK_CREATED(__io, __created, __attribute) \
do { \
CHECK_VAL ( ( __io ) - > out . create_action , NTCREATEX_ACTION_ # # __created ) ; \
CHECK_VAL ( ( __io ) - > out . alloc_size , 0 ) ; \
CHECK_VAL ( ( __io ) - > out . size , 0 ) ; \
CHECK_VAL ( ( __io ) - > out . file_attr , ( __attribute ) ) ; \
CHECK_VAL ( ( __io ) - > out . reserved2 , 0 ) ; \
} while ( 0 )
# define CHECK_HANDLE(__h1, __h2) \
do { \
CHECK_VAL ( ( __h1 ) - > data [ 0 ] , ( __h2 ) - > data [ 0 ] ) ; \
CHECK_VAL ( ( __h1 ) - > data [ 1 ] , ( __h2 ) - > data [ 1 ] ) ; \
} while ( 0 )
# define __IO_OUT_VAL(__io1, __io2, __m) \
CHECK_VAL ( ( __io1 ) - > out . __m , ( __io2 ) - > out . __m )
# define CHECK_CREATE_OUT(__io1, __io2) \
do { \
CHECK_HANDLE ( & ( __io1 ) - > out . file . handle , \
& ( __io2 ) - > out . file . handle ) ; \
__IO_OUT_VAL ( __io1 , __io2 , oplock_level ) ; \
__IO_OUT_VAL ( __io1 , __io2 , create_action ) ; \
__IO_OUT_VAL ( __io1 , __io2 , create_time ) ; \
__IO_OUT_VAL ( __io1 , __io2 , access_time ) ; \
__IO_OUT_VAL ( __io1 , __io2 , write_time ) ; \
__IO_OUT_VAL ( __io1 , __io2 , change_time ) ; \
__IO_OUT_VAL ( __io1 , __io2 , alloc_size ) ; \
__IO_OUT_VAL ( __io1 , __io2 , size ) ; \
__IO_OUT_VAL ( __io1 , __io2 , file_attr ) ; \
__IO_OUT_VAL ( __io1 , __io2 , durable_open ) ; \
__IO_OUT_VAL ( __io1 , __io2 , durable_open_v2 ) ; \
__IO_OUT_VAL ( __io1 , __io2 , persistent_open ) ; \
__IO_OUT_VAL ( __io1 , __io2 , timeout ) ; \
__IO_OUT_VAL ( __io1 , __io2 , blobs . num_blobs ) ; \
} while ( 0 )
# define BASEDIR "replaytestdir"
static struct {
struct torture_context * tctx ;
struct smb2_handle handle ;
uint8_t level ;
struct smb2_break br ;
int count ;
int failures ;
NTSTATUS failure_status ;
} break_info ;
static void torture_oplock_ack_callback ( struct smb2_request * req )
{
NTSTATUS status ;
status = smb2_break_recv ( req , & break_info . br ) ;
if ( ! NT_STATUS_IS_OK ( status ) ) {
break_info . failures + + ;
break_info . failure_status = status ;
}
return ;
}
/**
* A general oplock break notification handler . This should be used when a
* test expects to break from batch or exclusive to a lower level .
*/
static bool torture_oplock_ack_handler ( struct smb2_transport * transport ,
const struct smb2_handle * handle ,
uint8_t level ,
void * private_data )
{
struct smb2_tree * tree = private_data ;
const char * name ;
struct smb2_request * req ;
ZERO_STRUCT ( break_info . br ) ;
break_info . handle = * handle ;
break_info . level = level ;
break_info . count + + ;
switch ( level ) {
case SMB2_OPLOCK_LEVEL_II :
name = " level II " ;
break ;
case SMB2_OPLOCK_LEVEL_NONE :
name = " none " ;
break ;
default :
name = " unknown " ;
break_info . failures + + ;
}
torture_comment ( break_info . tctx ,
" Acking to %s [0x%02X] in oplock handler \n " ,
name , level ) ;
break_info . br . in . file . handle = * handle ;
break_info . br . in . oplock_level = level ;
break_info . br . in . reserved = 0 ;
break_info . br . in . reserved2 = 0 ;
req = smb2_break_send ( tree , & break_info . br ) ;
req - > async . fn = torture_oplock_ack_callback ;
req - > async . private_data = NULL ;
return true ;
}
/**
* Test what happens when SMB2_FLAGS_REPLAY_OPERATION is enabled for various
* commands . We want to verify if the server returns an error code or not .
*/
static bool test_replay1 ( struct torture_context * tctx , struct smb2_tree * tree )
{
bool ret = true ;
NTSTATUS status ;
struct smb2_handle h ;
uint8_t buf [ 200 ] ;
struct smb2_read rd ;
union smb_setfileinfo sfinfo ;
union smb_fileinfo qfinfo ;
union smb_ioctl ioctl ;
struct smb2_lock lck ;
struct smb2_lock_element el [ 2 ] ;
struct smb2_flush f ;
TALLOC_CTX * tmp_ctx = talloc_new ( tree ) ;
const char * fname = BASEDIR " \\ replay1.dat " ;
struct smb2_transport * transport = tree - > session - > transport ;
if ( smbXcli_conn_protocol ( transport - > conn ) < PROTOCOL_SMB3_00 ) {
torture_skip ( tctx , " SMB 3.X Dialect family required for "
" Replay tests \n " ) ;
}
ZERO_STRUCT ( break_info ) ;
break_info . tctx = tctx ;
tree - > session - > transport - > oplock . handler = torture_oplock_ack_handler ;
tree - > session - > transport - > oplock . private_data = tree ;
status = torture_smb2_testdir ( tree , BASEDIR , & h ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
smb2_util_close ( tree , h ) ;
smb2cli_session_start_replay ( tree - > session - > smbXcli ) ;
torture_comment ( tctx , " Try Commands with Replay Flags Enabled \n " ) ;
torture_comment ( tctx , " Trying create \n " ) ;
status = torture_smb2_testfile ( tree , fname , & h ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_VAL ( break_info . count , 0 ) ;
/*
* Wireshark shows that the response has SMB2_FLAGS_REPLAY_OPERATION
* flags set . The server should ignore this flag .
*/
torture_comment ( tctx , " Trying write \n " ) ;
status = smb2_util_write ( tree , h , buf , 0 , ARRAY_SIZE ( buf ) ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
f = ( struct smb2_flush ) {
. in . file . handle = h
} ;
torture_comment ( tctx , " Trying flush \n " ) ;
status = smb2_flush ( tree , & f ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
rd = ( struct smb2_read ) {
. in . file . handle = h ,
. in . length = 10 ,
. in . offset = 0 ,
. in . min_count = 1
} ;
torture_comment ( tctx , " Trying read \n " ) ;
status = smb2_read ( tree , tmp_ctx , & rd ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_VAL ( rd . out . data . length , 10 ) ;
sfinfo . generic . level = RAW_SFILEINFO_POSITION_INFORMATION ;
sfinfo . position_information . in . file . handle = h ;
sfinfo . position_information . in . position = 0x1000 ;
torture_comment ( tctx , " Trying setinfo \n " ) ;
status = smb2_setinfo_file ( tree , & sfinfo ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
qfinfo = ( union smb_fileinfo ) {
. generic . level = RAW_SFILEINFO_POSITION_INFORMATION ,
. generic . in . file . handle = h
} ;
torture_comment ( tctx , " Trying getinfo \n " ) ;
status = smb2_getinfo_file ( tree , tmp_ctx , & qfinfo ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_VAL ( qfinfo . position_information . out . position , 0x1000 ) ;
ioctl = ( union smb_ioctl ) {
. smb2 . level = RAW_IOCTL_SMB2 ,
. smb2 . in . file . handle = h ,
. smb2 . in . function = FSCTL_CREATE_OR_GET_OBJECT_ID ,
. smb2 . in . max_response_size = 64 ,
. smb2 . in . flags = SMB2_IOCTL_FLAG_IS_FSCTL
} ;
torture_comment ( tctx , " Trying ioctl \n " ) ;
status = smb2_ioctl ( tree , tmp_ctx , & ioctl . smb2 ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
lck = ( struct smb2_lock ) {
. in . locks = el ,
. in . lock_count = 0x0001 ,
. in . lock_sequence = 0x00000000 ,
. in . file . handle = h
} ;
el [ 0 ] . reserved = 0x00000000 ;
el [ 0 ] . flags = SMB2_LOCK_FLAG_EXCLUSIVE |
SMB2_LOCK_FLAG_FAIL_IMMEDIATELY ;
torture_comment ( tctx , " Trying lock \n " ) ;
el [ 0 ] . offset = 0x0000000000000000 ;
el [ 0 ] . length = 0x0000000000000100 ;
status = smb2_lock ( tree , & lck ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
lck . in . file . handle = h ;
el [ 0 ] . flags = SMB2_LOCK_FLAG_UNLOCK ;
status = smb2_lock ( tree , & lck ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_VAL ( break_info . count , 0 ) ;
done :
smb2cli_session_stop_replay ( tree - > session - > smbXcli ) ;
smb2_util_close ( tree , h ) ;
smb2_deltree ( tree , BASEDIR ) ;
talloc_free ( tmp_ctx ) ;
return ret ;
}
/**
* Test Durablity V2 Create Replay Detection on Single Channel . Also verify that
* regular creates can not be replayed .
*/
static bool test_replay2 ( struct torture_context * tctx , struct smb2_tree * tree )
{
NTSTATUS status ;
TALLOC_CTX * mem_ctx = talloc_new ( tctx ) ;
struct smb2_handle _h ;
struct smb2_handle * h = NULL ;
struct smb2_create io , ref1 , ref2 ;
struct GUID create_guid = GUID_random ( ) ;
uint32_t perms = 0 ;
bool ret = true ;
const char * fname = BASEDIR " \\ replay2.dat " ;
struct smb2_transport * transport = tree - > session - > transport ;
2016-01-15 18:07:31 +01:00
uint32_t share_capabilities ;
bool share_is_so ;
2014-08-07 12:52:50 +05:30
if ( smbXcli_conn_protocol ( transport - > conn ) < PROTOCOL_SMB3_00 ) {
torture_skip ( tctx , " SMB 3.X Dialect family required for "
" replay tests \n " ) ;
}
2016-01-15 18:07:31 +01:00
share_capabilities = smb2cli_tcon_capabilities ( tree - > smbXcli ) ;
share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT ;
2014-08-07 12:52:50 +05:30
ZERO_STRUCT ( break_info ) ;
break_info . tctx = tctx ;
tree - > session - > transport - > oplock . handler = torture_oplock_ack_handler ;
tree - > session - > transport - > oplock . private_data = tree ;
torture_comment ( tctx , " Replay of DurableHandleReqV2 on Single "
" Channel \n " ) ;
smb2_util_unlink ( tree , fname ) ;
status = torture_smb2_testdir ( tree , BASEDIR , & _h ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
smb2_util_close ( tree , _h ) ;
CHECK_VAL ( break_info . count , 0 ) ;
smb2_oplock_create_share ( & io , fname ,
smb2_util_share_access ( " " ) ,
smb2_util_oplock_level ( " b " ) ) ;
io . in . durable_open = false ;
io . in . durable_open_v2 = true ;
io . in . persistent_open = false ;
io . in . create_guid = create_guid ;
io . in . timeout = UINT32_MAX ;
status = smb2_create ( tree , mem_ctx , & io ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
ref1 = io ;
_h = io . out . file . handle ;
h = & _h ;
CHECK_CREATED ( & io , CREATED , FILE_ATTRIBUTE_ARCHIVE ) ;
CHECK_VAL ( io . out . durable_open , false ) ;
2016-01-15 18:07:31 +01:00
if ( share_is_so ) {
CHECK_VAL ( io . out . oplock_level , smb2_util_oplock_level ( " s " ) ) ;
CHECK_VAL ( io . out . durable_open_v2 , false ) ;
CHECK_VAL ( io . out . timeout , 0 ) ;
} else {
CHECK_VAL ( io . out . oplock_level , smb2_util_oplock_level ( " b " ) ) ;
CHECK_VAL ( io . out . durable_open_v2 , true ) ;
CHECK_VAL ( io . out . timeout , io . in . timeout ) ;
}
2014-08-07 12:52:50 +05:30
/*
* Replay Durable V2 Create on single channel
*/
smb2cli_session_start_replay ( tree - > session - > smbXcli ) ;
status = smb2_create ( tree , mem_ctx , & io ) ;
smb2cli_session_stop_replay ( tree - > session - > smbXcli ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_CREATE_OUT ( & io , & ref1 ) ;
CHECK_VAL ( break_info . count , 0 ) ;
/*
* See how server behaves if we change some of the Create params while
* Replaying . Change Share Access and Oplock Level . It seems the server
* does not care for change in these parameters . The server seems to
* only care for the File Name and GUID
*/
smb2_oplock_create_share ( & io , fname ,
smb2_util_share_access ( " RWD " ) ,
smb2_util_oplock_level ( " " ) ) ;
io . in . durable_open = false ;
io . in . durable_open_v2 = true ;
io . in . persistent_open = false ;
io . in . create_guid = create_guid ;
io . in . timeout = UINT32_MAX ;
/*
* The output will just react on the
* input , but it doesn ' t change the oplock
* or share access values on the existing open
*/
ref2 = ref1 ;
ref2 . out . oplock_level = smb2_util_oplock_level ( " " ) ;
ref2 . out . durable_open_v2 = false ;
ref2 . out . timeout = 0 ;
ref2 . out . blobs . num_blobs = 0 ;
smb2cli_session_start_replay ( tree - > session - > smbXcli ) ;
status = smb2_create ( tree , mem_ctx , & io ) ;
smb2cli_session_stop_replay ( tree - > session - > smbXcli ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_CREATE_OUT ( & io , & ref2 ) ;
CHECK_VAL ( break_info . count , 0 ) ;
/*
* This is a normal open , which triggers an oplock
* break and still gets NT_STATUS_SHARING_VIOLATION
*/
io = ref1 ;
io . in . durable_open_v2 = false ;
status = smb2_create ( tree , mem_ctx , & io ) ;
CHECK_STATUS ( status , NT_STATUS_SHARING_VIOLATION ) ;
2016-01-15 18:07:31 +01:00
if ( ! share_is_so ) {
CHECK_VAL ( break_info . count , 1 ) ;
CHECK_HANDLE ( & break_info . handle , & ref1 . out . file . handle ) ;
CHECK_VAL ( break_info . level , smb2_util_oplock_level ( " s " ) ) ;
ZERO_STRUCT ( break_info ) ;
}
2014-08-07 12:52:50 +05:30
smb2_util_close ( tree , * h ) ;
h = NULL ;
status = smb2_util_unlink ( tree , fname ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_VAL ( break_info . count , 0 ) ;
/*
* No Replay detection for regular Creates
*/
perms = SEC_STD_SYNCHRONIZE | SEC_STD_READ_CONTROL | SEC_STD_DELETE |
SEC_DIR_WRITE_ATTRIBUTE | SEC_DIR_READ_ATTRIBUTE |
SEC_DIR_WRITE_EA | SEC_FILE_APPEND_DATA |
SEC_FILE_WRITE_DATA ;
io = ( struct smb2_create ) {
. in . desired_access = perms ,
. in . file_attributes = 0 ,
. in . create_disposition = NTCREATEX_DISP_CREATE ,
. in . share_access = NTCREATEX_SHARE_ACCESS_DELETE ,
. in . create_options = 0x0 ,
. in . fname = fname
} ;
status = smb2_create ( tree , tctx , & io ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_VAL ( break_info . count , 0 ) ;
_h = io . out . file . handle ;
h = & _h ;
CHECK_CREATED ( & io , CREATED , FILE_ATTRIBUTE_ARCHIVE ) ;
torture_comment ( tctx , " No Replay Detection for regular Create \n " ) ;
/*
* Now replay the same create
*/
smb2cli_session_start_replay ( tree - > session - > smbXcli ) ;
status = smb2_create ( tree , tctx , & io ) ;
CHECK_STATUS ( status , NT_STATUS_OBJECT_NAME_COLLISION ) ;
CHECK_VAL ( break_info . count , 0 ) ;
done :
smb2cli_session_stop_replay ( tree - > session - > smbXcli ) ;
if ( h ! = NULL ) {
smb2_util_close ( tree , * h ) ;
}
smb2_deltree ( tree , BASEDIR ) ;
talloc_free ( tree ) ;
talloc_free ( mem_ctx ) ;
return ret ;
}
/**
* Test Durablity V2 Create Replay Detection on Multi Channel
*/
static bool test_replay3 ( struct torture_context * tctx , struct smb2_tree * tree1 )
{
const char * host = torture_setting_string ( tctx , " host " , NULL ) ;
const char * share = torture_setting_string ( tctx , " share " , NULL ) ;
struct cli_credentials * credentials = cmdline_credentials ;
NTSTATUS status ;
TALLOC_CTX * mem_ctx = talloc_new ( tctx ) ;
struct smb2_handle _h ;
struct smb2_handle * h = NULL ;
struct smb2_create io ;
struct GUID create_guid = GUID_random ( ) ;
bool ret = true ;
const char * fname = BASEDIR " \\ replay3.dat " ;
struct smb2_tree * tree2 = NULL ;
struct smb2_transport * transport1 = tree1 - > session - > transport ;
struct smb2_transport * transport2 = NULL ;
struct smb2_session * session1_1 = tree1 - > session ;
struct smb2_session * session1_2 = NULL ;
2016-01-15 18:24:08 +01:00
uint32_t share_capabilities ;
bool share_is_so ;
2014-08-07 12:52:50 +05:30
if ( smbXcli_conn_protocol ( transport1 - > conn ) < PROTOCOL_SMB3_00 ) {
torture_skip ( tctx , " SMB 3.X Dialect family required for "
" Replay tests \n " ) ;
}
2016-01-15 18:24:08 +01:00
share_capabilities = smb2cli_tcon_capabilities ( tree1 - > smbXcli ) ;
share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT ;
2014-08-07 12:52:50 +05:30
ZERO_STRUCT ( break_info ) ;
break_info . tctx = tctx ;
transport1 - > oplock . handler = torture_oplock_ack_handler ;
transport1 - > oplock . private_data = tree1 ;
torture_comment ( tctx , " Replay of DurableHandleReqV2 on Multi "
" Channel \n " ) ;
status = torture_smb2_testdir ( tree1 , BASEDIR , & _h ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
smb2_util_close ( tree1 , _h ) ;
smb2_util_unlink ( tree1 , fname ) ;
CHECK_VAL ( break_info . count , 0 ) ;
/*
* use the 1 st channel , 1 st session
*/
smb2_oplock_create_share ( & io , fname ,
smb2_util_share_access ( " " ) ,
smb2_util_oplock_level ( " b " ) ) ;
io . in . durable_open = false ;
io . in . durable_open_v2 = true ;
io . in . persistent_open = false ;
io . in . create_guid = create_guid ;
io . in . timeout = UINT32_MAX ;
tree1 - > session = session1_1 ;
status = smb2_create ( tree1 , mem_ctx , & io ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
_h = io . out . file . handle ;
h = & _h ;
CHECK_CREATED ( & io , CREATED , FILE_ATTRIBUTE_ARCHIVE ) ;
2016-01-15 18:24:08 +01:00
if ( share_is_so ) {
CHECK_VAL ( io . out . oplock_level , smb2_util_oplock_level ( " s " ) ) ;
CHECK_VAL ( io . out . durable_open_v2 , false ) ;
CHECK_VAL ( io . out . timeout , 0 ) ;
} else {
CHECK_VAL ( io . out . oplock_level , smb2_util_oplock_level ( " b " ) ) ;
CHECK_VAL ( io . out . durable_open_v2 , true ) ;
CHECK_VAL ( io . out . timeout , io . in . timeout ) ;
}
2014-08-07 12:52:50 +05:30
CHECK_VAL ( io . out . durable_open , false ) ;
CHECK_VAL ( break_info . count , 0 ) ;
status = smb2_connect ( tctx ,
host ,
lpcfg_smb_ports ( tctx - > lp_ctx ) ,
share ,
lpcfg_resolve_context ( tctx - > lp_ctx ) ,
credentials ,
& tree2 ,
tctx - > ev ,
& transport1 - > options ,
lpcfg_socket_options ( tctx - > lp_ctx ) ,
lpcfg_gensec_settings ( tctx , tctx - > lp_ctx )
) ;
torture_assert_ntstatus_ok_goto ( tctx , status , ret , done ,
" smb2_connect failed " ) ;
transport2 = tree2 - > session - > transport ;
transport2 - > oplock . handler = torture_oplock_ack_handler ;
transport2 - > oplock . private_data = tree2 ;
/*
* Now bind the 1 st session to 2 nd transport channel
*/
session1_2 = smb2_session_channel ( transport2 ,
lpcfg_gensec_settings ( tctx , tctx - > lp_ctx ) ,
tree2 , session1_1 ) ;
torture_assert ( tctx , session1_2 ! = NULL , " smb2_session_channel failed " ) ;
status = smb2_session_setup_spnego ( session1_2 ,
cmdline_credentials ,
0 /* previous_session_id */ ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
/*
* use the 2 nd channel , 1 st session
*/
tree1 - > session = session1_2 ;
smb2cli_session_start_replay ( tree1 - > session - > smbXcli ) ;
status = smb2_create ( tree1 , mem_ctx , & io ) ;
smb2cli_session_stop_replay ( tree1 - > session - > smbXcli ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
_h = io . out . file . handle ;
h = & _h ;
CHECK_CREATED ( & io , CREATED , FILE_ATTRIBUTE_ARCHIVE ) ;
2016-01-15 18:24:08 +01:00
if ( share_is_so ) {
CHECK_VAL ( io . out . oplock_level , smb2_util_oplock_level ( " s " ) ) ;
CHECK_VAL ( io . out . durable_open_v2 , false ) ;
CHECK_VAL ( io . out . timeout , 0 ) ;
} else {
CHECK_VAL ( io . out . oplock_level , smb2_util_oplock_level ( " b " ) ) ;
CHECK_VAL ( io . out . durable_open_v2 , true ) ;
CHECK_VAL ( io . out . timeout , io . in . timeout ) ;
}
2014-08-07 12:52:50 +05:30
CHECK_VAL ( io . out . durable_open , false ) ;
CHECK_VAL ( break_info . count , 0 ) ;
tree1 - > session = session1_1 ;
smb2_util_close ( tree1 , * h ) ;
h = NULL ;
done :
talloc_free ( tree2 ) ;
tree1 - > session = session1_1 ;
if ( h ! = NULL ) {
smb2_util_close ( tree1 , * h ) ;
}
smb2_util_unlink ( tree1 , fname ) ;
smb2_deltree ( tree1 , BASEDIR ) ;
talloc_free ( tree1 ) ;
talloc_free ( mem_ctx ) ;
return ret ;
}
/**
* Test Multichannel IO Ordering using ChannelSequence / Channel Epoch number
*/
static bool test_replay4 ( struct torture_context * tctx , struct smb2_tree * tree1 )
{
const char * host = torture_setting_string ( tctx , " host " , NULL ) ;
const char * share = torture_setting_string ( tctx , " share " , NULL ) ;
struct cli_credentials * credentials = cmdline_credentials ;
NTSTATUS status ;
TALLOC_CTX * mem_ctx = talloc_new ( tctx ) ;
struct smb2_handle _h1 ;
struct smb2_handle * h1 = NULL ;
struct smb2_create io ;
struct GUID create_guid = GUID_random ( ) ;
uint8_t buf [ 64 ] ;
struct smb2_read rd ;
union smb_setfileinfo sfinfo ;
bool ret = true ;
const char * fname = BASEDIR " \\ replay4.dat " ;
struct smb2_tree * tree2 = NULL ;
struct smb2_transport * transport1 = tree1 - > session - > transport ;
struct smb2_transport * transport2 = NULL ;
struct smb2_session * session1_1 = tree1 - > session ;
struct smb2_session * session1_2 = NULL ;
uint16_t curr_cs ;
if ( smbXcli_conn_protocol ( transport1 - > conn ) < PROTOCOL_SMB3_00 ) {
torture_skip ( tctx , " SMB 3.X Dialect family required for "
" Replay tests \n " ) ;
}
ZERO_STRUCT ( break_info ) ;
break_info . tctx = tctx ;
transport1 - > oplock . handler = torture_oplock_ack_handler ;
transport1 - > oplock . private_data = tree1 ;
torture_comment ( tctx , " IO Ordering for Multi Channel \n " ) ;
status = torture_smb2_testdir ( tree1 , BASEDIR , & _h1 ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
smb2_util_close ( tree1 , _h1 ) ;
smb2_util_unlink ( tree1 , fname ) ;
CHECK_VAL ( break_info . count , 0 ) ;
/*
* use the 1 st channel , 1 st session
*/
smb2_oplock_create_share ( & io , fname ,
smb2_util_share_access ( " " ) ,
smb2_util_oplock_level ( " b " ) ) ;
io . in . durable_open = false ;
io . in . durable_open_v2 = true ;
io . in . persistent_open = false ;
io . in . create_guid = create_guid ;
io . in . timeout = UINT32_MAX ;
tree1 - > session = session1_1 ;
status = smb2_create ( tree1 , mem_ctx , & io ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
_h1 = io . out . file . handle ;
h1 = & _h1 ;
CHECK_CREATED ( & io , CREATED , FILE_ATTRIBUTE_ARCHIVE ) ;
CHECK_VAL ( io . out . oplock_level , smb2_util_oplock_level ( " b " ) ) ;
CHECK_VAL ( io . out . durable_open , false ) ;
CHECK_VAL ( io . out . durable_open_v2 , true ) ;
CHECK_VAL ( io . out . timeout , io . in . timeout ) ;
CHECK_VAL ( break_info . count , 0 ) ;
status = smb2_util_write ( tree1 , * h1 , buf , 0 , ARRAY_SIZE ( buf ) ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
/*
* Increment ChannelSequence so that server thinks that there ' s a
* Channel Failure
*/
smb2cli_session_increment_channel_sequence ( tree1 - > session - > smbXcli ) ;
/*
* Perform a Read with incremented ChannelSequence
*/
rd = ( struct smb2_read ) {
. in . file . handle = * h1 ,
. in . length = sizeof ( buf ) ,
. in . offset = 0
} ;
status = smb2_read ( tree1 , tree1 , & rd ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
/*
* Performing a Write with Stale ChannelSequence is not allowed by
* server
*/
curr_cs = smb2cli_session_reset_channel_sequence (
tree1 - > session - > smbXcli , 0 ) ;
status = smb2_util_write ( tree1 , * h1 , buf , 0 , ARRAY_SIZE ( buf ) ) ;
CHECK_STATUS ( status , NT_STATUS_FILE_NOT_AVAILABLE ) ;
/*
* Performing a Write Replay with Stale ChannelSequence is not allowed
* by server
*/
smb2cli_session_start_replay ( tree1 - > session - > smbXcli ) ;
smb2cli_session_reset_channel_sequence ( tree1 - > session - > smbXcli , 0 ) ;
status = smb2_util_write ( tree1 , * h1 , buf , 0 , ARRAY_SIZE ( buf ) ) ;
smb2cli_session_stop_replay ( tree1 - > session - > smbXcli ) ;
CHECK_STATUS ( status , NT_STATUS_FILE_NOT_AVAILABLE ) ;
/*
* Performing a SetInfo with stale ChannelSequence is not allowed by
* server
*/
ZERO_STRUCT ( sfinfo ) ;
sfinfo . generic . level = RAW_SFILEINFO_POSITION_INFORMATION ;
sfinfo . generic . in . file . handle = * h1 ;
sfinfo . position_information . in . position = 0x1000 ;
status = smb2_setinfo_file ( tree1 , & sfinfo ) ;
CHECK_STATUS ( status , NT_STATUS_FILE_NOT_AVAILABLE ) ;
/*
* Performing a Read with stale ChannelSequence is allowed
*/
rd = ( struct smb2_read ) {
. in . file . handle = * h1 ,
. in . length = ARRAY_SIZE ( buf ) ,
. in . offset = 0
} ;
status = smb2_read ( tree1 , tree1 , & rd ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
status = smb2_connect ( tctx ,
host ,
lpcfg_smb_ports ( tctx - > lp_ctx ) ,
share ,
lpcfg_resolve_context ( tctx - > lp_ctx ) ,
credentials ,
& tree2 ,
tctx - > ev ,
& transport1 - > options ,
lpcfg_socket_options ( tctx - > lp_ctx ) ,
lpcfg_gensec_settings ( tctx , tctx - > lp_ctx )
) ;
torture_assert_ntstatus_ok_goto ( tctx , status , ret , done ,
" smb2_connect failed " ) ;
transport2 = tree2 - > session - > transport ;
transport2 - > oplock . handler = torture_oplock_ack_handler ;
transport2 - > oplock . private_data = tree2 ;
/*
* Now bind the 1 st session to 2 nd transport channel
*/
session1_2 = smb2_session_channel ( transport2 ,
lpcfg_gensec_settings ( tctx , tctx - > lp_ctx ) ,
tree2 , session1_1 ) ;
torture_assert ( tctx , session1_2 ! = NULL , " smb2_session_channel failed " ) ;
status = smb2_session_setup_spnego ( session1_2 ,
cmdline_credentials ,
0 /* previous_session_id */ ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
/*
* use the 2 nd channel , 1 st session
*/
tree1 - > session = session1_2 ;
/*
* Write Replay with Correct ChannelSequence is allowed by the server
*/
smb2cli_session_start_replay ( tree1 - > session - > smbXcli ) ;
smb2cli_session_reset_channel_sequence ( tree1 - > session - > smbXcli ,
curr_cs ) ;
status = smb2_util_write ( tree1 , * h1 , buf , 0 , ARRAY_SIZE ( buf ) ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
smb2cli_session_stop_replay ( tree1 - > session - > smbXcli ) ;
/*
* See what happens if we change the Buffer and perform a Write Replay .
* This is to show that Write Replay does not really care about the data
*/
memset ( buf , ' r ' , ARRAY_SIZE ( buf ) ) ;
smb2cli_session_start_replay ( tree1 - > session - > smbXcli ) ;
status = smb2_util_write ( tree1 , * h1 , buf , 0 , ARRAY_SIZE ( buf ) ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
smb2cli_session_stop_replay ( tree1 - > session - > smbXcli ) ;
/*
* Read back from File to verify what was written
*/
rd = ( struct smb2_read ) {
. in . file . handle = * h1 ,
. in . length = ARRAY_SIZE ( buf ) ,
. in . offset = 0
} ;
status = smb2_read ( tree1 , tree1 , & rd ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
if ( ( rd . out . data . length ! = ARRAY_SIZE ( buf ) ) | |
memcmp ( rd . out . data . data , buf , ARRAY_SIZE ( buf ) ) ) {
torture_comment ( tctx , " Write Replay Data Mismatch \n " ) ;
}
tree1 - > session = session1_1 ;
smb2_util_close ( tree1 , * h1 ) ;
h1 = NULL ;
CHECK_VAL ( break_info . count , 0 ) ;
done :
talloc_free ( tree2 ) ;
tree1 - > session = session1_1 ;
if ( h1 ! = NULL ) {
smb2_util_close ( tree1 , * h1 ) ;
}
smb2_util_unlink ( tree1 , fname ) ;
smb2_deltree ( tree1 , BASEDIR ) ;
talloc_free ( tree1 ) ;
talloc_free ( mem_ctx ) ;
return ret ;
}
2015-10-14 18:16:06 +05:30
/**
* Test Durablity V2 Persistent Create Replay on a Single Channel
*/
static bool test_replay5 ( struct torture_context * tctx , struct smb2_tree * tree )
{
NTSTATUS status ;
TALLOC_CTX * mem_ctx = talloc_new ( tctx ) ;
struct smb2_handle _h ;
struct smb2_handle * h = NULL ;
struct smb2_create io ;
struct GUID create_guid = GUID_random ( ) ;
bool ret = true ;
uint32_t share_capabilities ;
bool share_is_ca ;
2016-01-15 17:34:43 +01:00
bool share_is_so ;
2015-10-14 18:16:06 +05:30
const char * fname = BASEDIR " \\ replay5.dat " ;
struct smb2_transport * transport = tree - > session - > transport ;
struct smbcli_options options = tree - > session - > transport - > options ;
2016-01-15 17:34:43 +01:00
uint8_t expect_oplock = smb2_util_oplock_level ( " b " ) ;
NTSTATUS expect_status = NT_STATUS_DUPLICATE_OBJECTID ;
2015-10-14 18:16:06 +05:30
if ( smbXcli_conn_protocol ( transport - > conn ) < PROTOCOL_SMB3_00 ) {
torture_skip ( tctx , " SMB 3.X Dialect family required for "
" Replay tests \n " ) ;
}
share_capabilities = smb2cli_tcon_capabilities ( tree - > smbXcli ) ;
2016-01-15 17:34:43 +01:00
2015-10-14 18:16:06 +05:30
share_is_ca = share_capabilities & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY ;
if ( ! share_is_ca ) {
torture_skip ( tctx , " Persistent File Handles not supported " ) ;
}
2016-01-15 17:34:43 +01:00
share_is_so = share_capabilities & SMB2_SHARE_CAP_SCALEOUT ;
if ( share_is_so ) {
expect_oplock = smb2_util_oplock_level ( " s " ) ;
expect_status = NT_STATUS_FILE_NOT_AVAILABLE ;
}
2015-10-14 18:16:06 +05:30
ZERO_STRUCT ( break_info ) ;
break_info . tctx = tctx ;
transport - > oplock . handler = torture_oplock_ack_handler ;
transport - > oplock . private_data = tree ;
torture_comment ( tctx , " Replay of Persistent DurableHandleReqV2 on Single "
" Channel \n " ) ;
status = torture_smb2_testdir ( tree , BASEDIR , & _h ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
smb2_util_close ( tree , _h ) ;
smb2_util_unlink ( tree , fname ) ;
CHECK_VAL ( break_info . count , 0 ) ;
smb2_oplock_create_share ( & io , fname ,
smb2_util_share_access ( " RWD " ) ,
smb2_util_oplock_level ( " b " ) ) ;
io . in . durable_open = false ;
io . in . durable_open_v2 = true ;
io . in . persistent_open = true ;
io . in . create_guid = create_guid ;
io . in . timeout = UINT32_MAX ;
status = smb2_create ( tree , mem_ctx , & io ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
_h = io . out . file . handle ;
h = & _h ;
CHECK_CREATED ( & io , CREATED , FILE_ATTRIBUTE_ARCHIVE ) ;
2016-01-15 17:34:43 +01:00
CHECK_VAL ( io . out . oplock_level , expect_oplock ) ;
2015-10-14 18:16:06 +05:30
CHECK_VAL ( io . out . durable_open , false ) ;
CHECK_VAL ( io . out . durable_open_v2 , true ) ;
CHECK_VAL ( io . out . persistent_open , true ) ;
CHECK_VAL ( io . out . timeout , io . in . timeout ) ;
CHECK_VAL ( break_info . count , 0 ) ;
/* disconnect, leaving the durable open */
TALLOC_FREE ( tree ) ;
if ( ! torture_smb2_connection_ext ( tctx , 0 , & options , & tree ) ) {
torture_warning ( tctx , " couldn't reconnect, bailing \n " ) ;
ret = false ;
goto done ;
}
/* a re-open of a persistent handle causes an error */
status = smb2_create ( tree , mem_ctx , & io ) ;
2016-01-15 17:34:43 +01:00
CHECK_STATUS ( status , expect_status ) ;
2015-10-14 18:16:06 +05:30
/* SMB2_FLAGS_REPLAY_OPERATION must be set to open the Persistent Handle */
smb2cli_session_start_replay ( tree - > session - > smbXcli ) ;
smb2cli_session_increment_channel_sequence ( tree - > session - > smbXcli ) ;
status = smb2_create ( tree , mem_ctx , & io ) ;
CHECK_STATUS ( status , NT_STATUS_OK ) ;
CHECK_CREATED ( & io , CREATED , FILE_ATTRIBUTE_ARCHIVE ) ;
CHECK_VAL ( io . out . durable_open , false ) ;
CHECK_VAL ( io . out . persistent_open , true ) ;
2016-01-15 17:34:43 +01:00
CHECK_VAL ( io . out . oplock_level , expect_oplock ) ;
2015-10-14 18:16:06 +05:30
_h = io . out . file . handle ;
h = & _h ;
smb2_util_close ( tree , * h ) ;
h = NULL ;
done :
if ( h ! = NULL ) {
smb2_util_close ( tree , * h ) ;
}
smb2_util_unlink ( tree , fname ) ;
smb2_deltree ( tree , BASEDIR ) ;
talloc_free ( tree ) ;
talloc_free ( mem_ctx ) ;
return ret ;
}
2014-08-07 12:52:50 +05:30
struct torture_suite * torture_smb2_replay_init ( void )
{
struct torture_suite * suite =
torture_suite_create ( talloc_autofree_context ( ) , " replay " ) ;
torture_suite_add_1smb2_test ( suite , " replay1 " , test_replay1 ) ;
torture_suite_add_1smb2_test ( suite , " replay2 " , test_replay2 ) ;
torture_suite_add_1smb2_test ( suite , " replay3 " , test_replay3 ) ;
torture_suite_add_1smb2_test ( suite , " replay4 " , test_replay4 ) ;
2015-10-14 18:16:06 +05:30
torture_suite_add_1smb2_test ( suite , " replay5 " , test_replay5 ) ;
2014-08-07 12:52:50 +05:30
suite - > description = talloc_strdup ( suite , " SMB2 REPLAY tests " ) ;
return suite ;
}