Unexpected behaviour of the RFC call
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?
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:
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?
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.
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.
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:
- 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.
LOAD-OF-PROGRAM, global variables, RFC connection without login information - this just reads like one of those horror movies where the characters keep doing irrational things that get everyone in trouble eventually.
What is wrong with LOAD-OF-PROGRAM event and global variables? What can be used instead?
Thanks in advance.
I think you'll find a lot of information on "what's wrong with global variables" online and probably in Clean ABAP in particular. And as I said, then it's just one thing leads to another: LOAD-OF-PROGRAM is used to clean global variables and then leads to the effects you're describing.
To be honest, I didn't dive deep in all the blog details but it does seem that SAP should refactor the code there.
Thank you for your answer. usage of class attributes for same purpose won't help here. I am not a developer, too young for Clean ABAP, but if I decide to become a developer, for sure I read it.
Load of program event is used not for cleanup in standard, it is used to notify transaction manager class that "function group xxx" was used, and therefore when cleanup is called some functions should be called.
It is actually nice solution, a lot of functional modules are registered via transaction manager class if used in the application. As for solution it is relatively new, and described issue came up with a recent changes S4Hana with different classes based on central/decentral settings.
I used global variable to simplify example, SAP use singleton classes. Since it is RFC functionality, functional module is used, and since it is functionality with involved transaction manager cleanup must be registered somewhere. For sure all this coming from old days with functional programming, it works pretty well. Issue happens only because of the code execution in a wrong client.
Clean ABAP won't help if class constructor would be called in the client 000, and therefore object would be initialized with wrong information.
I can agree, this post is not about coding, I just added ABAP development tag, because it is good to know this when you call standard functional module by RFC, and all works well till user lock himself in the system or something like this. Theoretically as an easy solution own written RFC functional module as a wrapper can be used. But I hoped that SAP fix RFC call flow.
I am interested how static constructor(s) (CLASS_CONSTRUCTOR) behave in this constellation. Is it executed in 000 client?
Anyway, did you raise a ticket with SAP? IMO, it seems like a potential security loophole.
Since FM itself is called only after authorization, code based in the FM executed normally, in correct client.
If class used in load of program section, it is in executed before users logon (in case of manual logon). Class constructor in this case executed in client 000 with empty user. You can see example in "Real life example" there is exactly goes about class constructor.
Sure, OSS message was raised, as well as I notified security team. But without any results for now. Unfortunately I do not have access to the OSS since it was raised from the client.
There are always exceptions to the rule. A stateful session can still be very useful (so, with global variables). It's the principle of update BAPIs by RFC, the kernel holds information about the current session, holds the update function modules with parameters, that is used during the second call to BAPI_TRANSACTION_COMMIT. Also, the BAPI itself may register BUFFER_SUBSCRIBE_FOR_REFRESH in case of rollback. It's also needed in case high-performance is required, like a database cursor which is opened once, several RFC calls are needed to read the whole data because the calling application may require to manage a timeout. The cursor variable is defined as global. For instance, the BW extractors are based on that principle. ICF also offers the possibility of having stateful sessions with ICF/HTTP connections (BSPs and anything you want).
But I agree with the rule-of-thumb that global variables must not be used (I remember all these "horror-movie-like" programs). The recommendation is to use stateless sessions (REST, etc.)
Nice investigation. Certainly seems like a security issue to me.
If I understand correctly, the TL;DR of this is:
The LOAD-OF-PROGRAM event runs in client 000 for RFC over connections that are either trusted or where credentials are supplied.
And the take-home is: don't use LOAD-OF-PROGRAM in function groups with RFC enabled function modules. (In over twenty years of ABAP development, I've never used LOAD-OF-PROGRAM anyway).
Instead of current title "strange RFC behavior" (means nothing to me, it didn't attract my attention initially so I had not read this blog post at the time it was published), I'd prefer a title like your "take-home". And it's exactly what I retain from this behavior, I will never use LOAD-OF-PROGRAM with RFC.
Daniil Mazurkevich Thank you for the discovery and the explanation.
Where credentials are NOT supplied,or not valid. So when user should manually provide login/password. And usually it works well till for example user is locked, password expired or strange case with manual password input.
Problem when you call standard FM which use load of program.
In EWM are several such FM’s (maybe 20). One which are described in real example called from ERP with dialogue RFC connection, normally it is trusted connection or manual password input. But this problem is only came up with s4
Hi Daniil Mazurkevich
Thank you for sharing.