This article aims to briefly address the often-discussed issue of data lifetime under the topic of poor structures/strategies in safety software, as it is fundamental and important, and indeed a recurring problem. However, it has turned into a longer discussion. Since this section only raises issues, the logic cannot be considered rigorous.
The data lifetime problem has evidently become a persistent issue that has been repeatedly proven to be significant:
Among the safety incidents related to railway safety critical products that I am aware of, including those that have resulted in serious accidents and those that have shown conditions for serious accidents but did not manifest due to fortunate circumstances, a considerable portion is related to data lifetime. Specifically, expired data that can no longer reflect the current line conditions is still used by control systems as reliable data, leading to erroneous output results. If the supplier systematically neglects this issue, it could be considered a design bug; if the supplier has sufficient experience but still encounters this problem, it could be considered an implementation bug. Interestingly, regardless of the cause, and regardless of how many false alarms or real incidents this issue has caused, many design implementations we observe still do not adequately address this problem, which continues to appear repeatedly in design implementations, either already existing or emerging during future software maintenance and expansion.
Data Lifetime Problem:
In software logic processing, since some data must inevitably be temporarily stored for processing in the next cycle or for use in the upcoming period, we can only try to limit the use of unnecessary global caches and static caches. Improving the habit of excessively using global caches and static caches, and designing to use local caches wherever possible (for example, using dual CPU for receiving and sending during the same instance, completing the reception and transmission without retention), is essential, although we cannot completely avoid using global caches and static caches. Global caches and static caches are the source of data expiration; if old data inside is not processed in a timely manner, it will persist until it is used incorrectly.
Several In-depth Issues Regarding Data Lifetime:
It is important to note that some external inputs that we may consider to exist in a “dynamic” form may ultimately remain “static”. An obvious example is the cessation of data communication, while less obvious cases include disconnections of external IO inputs or the loss of certain interrupt responses. In such cases, whether the data at the starting point of the data cache chain has already expired is worth attention. We often mistakenly believe that those cache locations are unconditionally refreshed, but is that really the case?
Another point to note is that all data cache points on safety-related data links from different input ports to different output ports (including intermediate fetching processes) need to be examined. The reasoning is simple: the interconnections between these data cache points are always intertwined with numerous conditions and judgments, essentially interspersing all the judgment logic from input to processing to output. A data cache point may be bypassed in previous cycles and directly used in subsequent cycles. Therefore, if a cached data point on this data chain expires, it means that all downstream caches will be reset to expired data, regardless of whether the previous data downstream has been properly processed.
Another obvious issue is that the data lifetime problem is clearly a systemic failure issue, which also means that in most cases, it is outside the protective scope of 2 fetching 2.
Mitigation Measures:
So far, the most effective method is to create a timer for each potentially expired data cache, which unconditionally clears the data when the timer times out. In many current designs, either insufficient consideration is given to data expiration, or “clear before use” or “clear after use” methods are employed. The fact is that whether “clear before use” or “clear after use” is effective only under a small number of stringent conditions; in reality, whether this mechanism can “timely” clear expired data entirely depends on when it is “used”. As long as the timing of “use” is inappropriate, it is ineffective. Moreover, the timing of being “used” depends on the complete logical conditions served by the entire data flow. (We use the term “being used” to encompass both subjective intentions and objective conditions triggering it). The most critical issue is that this “being used timing” is dynamically variable throughout the entire software maintenance lifecycle. An inadvertent later software change, such as adding an entirely unrelated judgment condition, could be crucial for whether the subsequent data cache can be cleared in a timely manner. During changes, no one pays attention to this, or there may not be sufficient checks to focus on this point.
It should be noted that the above caches not only exist in the caches directly controlled by the CPU, but also the caches in the communication controller and UPIC should be considered similarly when they cannot be fully assumed to be transparent.
Current Status:
When we evaluate code through sampling, we deliberately pay attention to this point. No design-related documents will detail to this extent, so code reviews are the most effective method. However, “good implementations are always uniform, while poor implementations are always unique”; if a cache is not effectively cleared by a timely mechanism, then checking for “not effectively covered by a mechanism yet still possibly accepted for other reasons” becomes a very time-consuming and labor-intensive task. This is equivalent to checking all branch conditions for writing to it (which may require tracing back to the starting point of the main function) and all branch conditions for reading from it. Moreover, confirmation can only be done at the code level: often, the same logical conditions are stated, but the flag conditions or the locations of conditional judgments used in code processing are still different (different locations imply that there may be additional conditional judgment branches in between).
It can be seen that falsifying is easy, but proving requires considerable effort and detail. Unfortunately, under the current situation where frontline developers may not pay sufficient attention to this, the reality in the industry is that internal project code reviews or design reviews, due to time and experience limitations, also find it difficult to fully cover this issue. Furthermore, there are very few evaluation agencies capable of checking this issue, and even if they exist, due to time and cost considerations, they can only briefly touch on this topic during technical discussions (for example, one morning). Whether substantial findings can be made is limited by the evaluators’ experience, ability, and quick understanding of the supplier’s code, and sometimes even limited by the familiarity of the audited personnel with the code. There will be no time or opportunity for further in-depth understanding, global consideration, and meticulous investigation. Whether it works or not depends solely on the evaluator’s momentary judgment.
This may be one of the reasons for the issue of “no one is wrong, yet the result is wrong”.
Recommendations:
R&D units, pay more attention;
Evaluation agencies, rely more on accuracy;
Regulatory agencies, increase special regulatory efforts.