In this post I would like to describe interesting behavior of the RFC functional call in ABAP, and especially the program flow on the target side.
Let’s start from the coding, you can find below small synthetic example which is easier to understand than real cases. So please do not focus on the deep sense of it, rather pay attention to the coding itself.
Here is a function group with LOAD-OF-PROGRAM event
DATA gr_usr02 TYPE rseloption.
LOAD-OF-PROGRAM.
SELECT
bname as low,
'I' as sign,
'EQ' as option
FROM usr02 INTO CORRESPONDING FIELDS OF TABLE @gr_usr02.
With a functional module, which performs deletion of the users which are not in the selected range:
FUNCTION z_rfc_test.
IF gr_usr02 IS NOT INITIAL.
DELETE FROM usr02 WHERE bname NOT IN @gr_usr02.
ENDIF.
ENDFUNCTION.
And we call this function remotely (For sure our function module is Remote-Enabled):
CALL FUNCTION 'Z_RFC_TEST'
DESTINATION 'target_rfc'
EXCEPTIONS
communication_failure = 1
system_failure = 2
not_exist = 3
OTHERS = 99.
IF sy-subrc NE 0.
WRITE `connection error ` && sy-subrc.
ENDIF.
What would happen if we call this function module?
Since key field is used, expected behavior is that nothing will be deleted This is not about cases when user was created between select and delete operators, to simplify the example even more, let’s suggest that only we are working in the system during the test.
So let’s check results before the execution of DELETE command, and here it is, modified functional module which returns two values, count of records and count of records based on the range:
FUNCTION z_rfc_test.
*"----------------------------------------------------------------------
*"*"Local Interface:
*" EXPORTING
*" VALUE(RV_TOTAL) TYPE SYST-DBCNT
*" VALUE(RV_TOBE_DELETED) TYPE SYST-DBCNT
*"----------------------------------------------------------------------
SELECT COUNT(*) FROM usr02.
rv_total = sy-dbcnt.
SELECT COUNT(*) FROM usr02 WHERE bname NOT IN @gr_usr02.
rv_tobe_deleted = sy-dbcnt.
ENDFUNCTION.
CALL FUNCTION 'Z_RFC_TEST'
DESTINATION 'target_rfc'
IMPORTING
rv_total = lv_total
rv_tobe_deleted = lv_tobe_deleted
EXCEPTIONS
communication_failure = 1
system_failure = 2
not_exist = 3
OTHERS = 99.
IF sy-subrc NE 0.
WRITE `connection error ` && sy-subrc.
ENDIF.
WRITE / 'TOTAL:' && lv_total .
WRITE / 'TOBE DELETED:' && lv_tobe_deleted.
Two calls. Same code and slightly changed settings in the system between executions:
Let’s start from the coding, you can find below small synthetic example which is easier to understand than real cases. So please do not focus on the deep sense of it, rather pay attention to the coding itself.
Here is a function group with LOAD-OF-PROGRAM event
DATA gr_usr02 TYPE rseloption.
LOAD-OF-PROGRAM.
SELECT
bname as low,
'I' as sign,
'EQ' as option
FROM usr02 INTO CORRESPONDING FIELDS OF TABLE @gr_usr02.
With a functional module, which performs deletion of the users which are not in the selected range:
FUNCTION z_rfc_test.
IF gr_usr02 IS NOT INITIAL.
DELETE FROM usr02 WHERE bname NOT IN @gr_usr02.
ENDIF.
ENDFUNCTION.
And we call this function remotely (For sure our function module is Remote-Enabled):
CALL FUNCTION 'Z_RFC_TEST'
DESTINATION 'target_rfc'
EXCEPTIONS
communication_failure = 1
system_failure = 2
not_exist = 3
OTHERS = 99.
IF sy-subrc NE 0.
WRITE `connection error ` && sy-subrc.
ENDIF.
What would happen if we call this function module?
Some tests in a system
Since key field is used, expected behavior is that nothing will be deleted This is not about cases when user was created between select and delete operators, to simplify the example even more, let’s suggest that only we are working in the system during the test.
So let’s check results before the execution of DELETE command, and here it is, modified functional module which returns two values, count of records and count of records based on the range:
FUNCTION z_rfc_test.
*"----------------------------------------------------------------------
*"*"Local Interface:
*" EXPORTING
*" VALUE(RV_TOTAL) TYPE SYST-DBCNT
*" VALUE(RV_TOBE_DELETED) TYPE SYST-DBCNT
*"----------------------------------------------------------------------
SELECT COUNT(*) FROM usr02.
rv_total = sy-dbcnt.
SELECT COUNT(*) FROM usr02 WHERE bname NOT IN @gr_usr02.
rv_tobe_deleted = sy-dbcnt.
ENDFUNCTION.
CALL FUNCTION 'Z_RFC_TEST'
DESTINATION 'target_rfc'
IMPORTING
rv_total = lv_total
rv_tobe_deleted = lv_tobe_deleted
EXCEPTIONS
communication_failure = 1
system_failure = 2
not_exist = 3
OTHERS = 99.
IF sy-subrc NE 0.
WRITE `connection error ` && sy-subrc.
ENDIF.
WRITE / 'TOTAL:' && lv_total .
WRITE / 'TOBE DELETED:' && lv_tobe_deleted.
Two calls. Same code and slightly changed settings in the system between executions:
Expected
Little bit unexpected
So you have two results, which one is to accept and which one is for the client to present?
Answer and explanation:
Why is it like this?! Why are there two different results, why is 142, and not 0 or at least not 149?
Let’s see…
The difference between the first and the second case is that in the second case I haven’t had login data specified in the RFC connection and because of this I got a logon screen, where I entered user and password from the target system.
There are different options for the RFC connection, and it can be customized with a technical user (from the target system), or as a trusted connection between systems in the same landscape, or user can input his password manually. So, user enter password manually in following cases:
◉ logon information is not set in the RFC connection;
◉ if any login problem arise like for example:
◉ wrong password;
◉ expired password;
◉ user is locked in a target system;
SM59:RFC Destination
And here is the difference in the SAP NetWeaver logic (at least how I understood it), between two Call Flows for processes without and with logon screen:
RFC Call flow
In case when user must provide user/password information LOAD-OF-PROGRAM is called, in the client 000 with empty user and it happens independent of user login (success/failed/cancelled).
For sure there is different number of users in the work and template (000) clients available, therefore such an interesting result is produced with this small functional module.
Real life example
(feel free to skip it, it is EWM related, and has a long boring explanation and all the pain which I got investigating it)
The reason of this research was unexpected behavior in the EWM process with QM/QIE integration.
During Partial usage decision user jumps with dialogue RFC connection from ERP to EWM, and if it is a process with manual login, S4 EWM system behaves as Central S4 instance.
Functional module ‘/SCWM/QUI_INSP_RFC’ belongs to the functional group which register cleanup with /SCWM/* transaction manager class.
LOAD-OF-PROGRAM for QM FM
Transaction manager in the class constructor calls Adapter Framework singleton class /SCDL/CL_AF_MANAGEMENT
/SCDL/CL_AF_MANAGEMENT in its own constructor calls Factory class /SCWM/CL_TM_FACTORY with interface ‘/SCWM/IF_TM_GLOBAL_INFO’.
Factory class has hard-coded values for the service providers, and corresponding class for the mentioned interface is /SCWM/CL_TM_GLOBAL_INFO, which initialized with factory /SCWM/CL_TM_GLOBAL_INFO, and in the constructor it calls one more singleton class /SCMB/CL_SYS_INFO:
/SCWM/CL_TM_GLOBAL_INFO, and in the constructor it calls one mire singleton class /SCMB/CL_SYS_INFO:
Which finally calls Functional Module /SCWM/T_DECENTRAL_READ in its own constructor:
this functional module reads decentral flag from the customizing, client dependent table /SCWM/TGLOBAL_C, and this table was (what a surprise) not maintained in the client 000.
Because of this, after user logged in, the system acted as a Central system, and with several other bugs, like missing error handling system performed reading of all the stock in all warehouses.
And this is how it looks like in the trace:
Wrapping it all up
Not sure that redesign of the RFC Call flow is planned by SAP Team. Anyway, it was an interesting quest to find all this and to understand a root cause.
Since code is executed in the system without actual user, this issue was reported to the SAP Security team, but it was not classified as a security issue, therefore I decided to share this information with community.
Following problems may occur:
1. Code execution without authorization (including database updates);
2. DoS attack in case of heavy executable part in the LOAD-OF-PROGRAM (without user authorization);
3. Program misbehavior e.g. customizing, or authorization checks from the client 000, used in a normal program run;
Suggested program flow, which can resolve described issue:
No comments:
Post a Comment