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:
Little bit unexpected
So you have two results, which one is to accept and which one is for the client to present?
Why is it like this?! Why are there two different results, why is 142, and not 0 or at least not 149?
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;
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:
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.
(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.
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:
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:
- Code execution without authorization (including database updates);
- DoS attack in case of heavy executable part in the LOAD-OF-PROGRAM (without user authorization);
- 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:
SAP help for LOAD-OF-PROGRAM event:
Some interesting parts from the LOAD-OF-PROGRAM SAP Help to ABAP:
• If a program is only loaded because declarations are required from it, such as when using absolute type names, the LOAD-OF-PROGRAM event is not raised. The program constructor is only executed if an executable unit of the program is called afterwards.
And the example will in case of RFC Logon screen always will have g_langy = ‘E’ since sy-langy is initial.
g_langu = COND #( WHEN sy-langu = 'D' THEN 'D' ELSE 'E' ).
See full help topic content here: help.sap.com
Example in standard
It is not so easy to analyze all the problems in the LOAD-OF-PROGRAM section, it is even more complex with OO-ABAP. Small research of the standard has shown for example functional module /SAPAPO/HEU_PLAN_OBJECTS_RFC. Call of this functional module via RFC with user logon screen performs insert into database table rpm_settings_gl (feel free to test, you can even cancel user logon, with Shift+F3), record anyway be inserted if table was empty in the client 000).
And you can do the trace to see this insert:
Sorry for some German screen-shots, and I would be happy to get some of your opinions regarding this issue.