Detection Rules

LadderScan checks your PLC code against these security and safety rules.

38
Total Rules
12
Critical
18
High
8
Medium

Basic Safety

LS001 AST Infinite Loop Critical

Detects WHILE or FOR loops where the loop variable is never modified, causing the PLC to hang indefinitely. This can halt all control logic and create dangerous conditions.

Example (Bad)

WHILE i < 10 DO acc := acc + 1; (* BUG: i never incremented! *) END_WHILE;

Fix

WHILE i < 10 DO acc := acc + 1; i := i + 1; (* Increment loop variable *) END_WHILE;
LS002 AST Missing Emergency Stop Critical

Detects when an emergency stop input is declared but never checked in the control logic. E-stops must always be able to halt equipment regardless of program state.

Example (Bad)

VAR_INPUT EmergencyStop : BOOL; END_VAR (* E-stop declared but never checked! *) IF Start THEN Motor := TRUE; END_IF;

Fix

IF EmergencyStop THEN Motor := FALSE; ELSIF Start AND NOT EmergencyStop THEN Motor := TRUE; END_IF;
LS003 AST Fail-Open Safety Logic Critical

Detects safety-critical outputs (valves, brakes) that default to TRUE (open/disengaged). Safety devices should fail to the safe state (closed/engaged) on power loss.

Example (Bad)

VAR SafetyValve : BOOL := TRUE; (* Fails OPEN! *) END_VAR

Fix

VAR SafetyValve : BOOL := FALSE; (* Fails CLOSED (safe) *) END_VAR
LS101 AST Latch Without Reset High

Detects outputs that are set TRUE but never set FALSE. This can cause actuators to remain energized indefinitely, leading to equipment damage or safety hazards.

Example (Bad)

IF Start THEN FillValve := TRUE; (* Never reset to FALSE! *) END_IF;

Fix

IF Level >= 100 THEN FillValve := FALSE; ELSIF Start THEN FillValve := TRUE; END_IF;

Timing & State

LS103 AST Timer Not Reset High

Detects TON/TOF timers that are enabled but never reset. This can cause timing logic to malfunction on subsequent cycles.

Example (Bad)

DelayTimer(IN := Start, PT := T#5S); IF DelayTimer.Q THEN Output := TRUE; (* Timer never reset! *) END_IF;

Fix

DelayTimer(IN := Start AND NOT Done, PT := T#5S); IF DelayTimer.Q THEN Output := TRUE; Done := TRUE; (* Resets timer input *) END_IF;
LS104 AST Potential Deadlock High

Detects circular wait conditions where two or more operations wait for each other, causing the system to hang.

Example (Bad)

(* A waits for B, B waits for A *) IF WaitForB THEN ProcessA(); END_IF; IF WaitForA THEN ProcessB(); END_IF;
LS105 AST State Machine Stuck High

Detects state machines without timeout handling or error states. If an expected event never occurs, the machine stays stuck indefinitely.

Example (Bad)

CASE State OF 0: IF Start THEN State := 1; END_IF; 1: Valve := TRUE; IF Done THEN State := 2; END_IF; (* No timeout! *) 2: State := 0; END_CASE;

Fix

CASE State OF 1: Valve := TRUE; StateTimer(IN:=TRUE, PT:=T#30S); IF Done OR StateTimer.Q THEN State := 2; END_IF; END_CASE;
LS118 AST Unsafe Startup State High

Detects outputs that initialize to unsafe states on PLC startup or after a power cycle. All safety-critical outputs should default to safe state.

Example (Bad)

VAR HeaterOn : BOOL := TRUE; (* Starts ON! *) END_VAR

Fix

VAR HeaterOn : BOOL := FALSE; (* Safe default *) StartupComplete : BOOL := FALSE; END_VAR (* Only enable after safety check *) IF StartupComplete AND TempBelowMax THEN HeaterOn := HeaterCmd; END_IF;

Data Integrity

LS004 AST Division by Zero Critical

Detects division operations without checking if the divisor could be zero, which causes runtime faults on most PLCs.

Example (Bad)

Rate := Volume / Time; (* Crash if Time = 0! *)

Fix

IF Time > 0.0 THEN Rate := Volume / Time; ELSE Rate := 0.0; END_IF;
LS005 AST Array Index Out of Bounds Critical

Detects array access using variables without bounds checking. Out-of-range access can corrupt memory or crash the PLC.

Example (Bad)

Speed := Speeds[RecipeNum]; (* No bounds check! *)

Fix

IF RecipeNum >= 0 AND RecipeNum <= 9 THEN Speed := Speeds[RecipeNum]; END_IF;
LS107 AST Integer Overflow High

Detects integer variables that are incremented without overflow protection. INT values wrap at 32767, causing counters to reset unexpectedly.

Example (Bad)

Total := Total + 1; (* Overflows at 32767! *)

Fix

IF Total < 32000 THEN Total := Total + 1; END_IF;
LS112 AST Uninitialized Variable Use High

Detects local variables that are read before being assigned a value. Uninitialized variables may contain arbitrary data, leading to unpredictable behavior in safety-critical systems.

Example (Bad)

VAR Result : REAL; Factor : REAL; END_VAR Output := Result * Factor; (* Both uninitialized! *)

Fix

VAR Result : REAL := 0.0; Factor : REAL := 1.0; END_VAR Output := Result * Factor;
LS113 AST Missing CASE ELSE High

Detects CASE statements without an ELSE default branch. Unhandled selector values may leave outputs in undefined states. Severity bumps to Critical when the selector is a state machine variable.

Example (Bad)

CASE State OF 0: (* IDLE *) Output := FALSE; 1: (* RUN *) Output := TRUE; END_CASE; (* What if State = 2? *)

Fix

CASE State OF 0: Output := FALSE; 1: Output := TRUE; ELSE Output := FALSE; (* Safe default *) FaultFlag := TRUE; END_CASE;

Input & Validation

LS106 AST No Input Validation High

Detects HMI or external inputs used directly without range validation. Malicious or erroneous values could cause unsafe operation.

Example (Bad)

Setpoint := HMI_Setpoint; (* No validation! *)

Fix

IF HMI_Setpoint >= 0 AND HMI_Setpoint <= 1000 THEN Setpoint := HMI_Setpoint; END_IF;
LS108 AST Setpoint Out of Range High

Detects setpoints assigned from external sources without range validation. Out-of-range values could damage equipment or product.

Example (Bad)

TempSetpoint := HMI_Input; (* No range check! *)

Fix

IF HMI_Input >= MIN_TEMP AND HMI_Input <= MAX_TEMP THEN TempSetpoint := HMI_Input; END_IF;
LS114 AST Unrestricted Loop Bounds High

Detects FOR loops with variable end bounds that are not validated, and WHILE loops with unbounded conditions. Unchecked loop bounds from external inputs could cause millions of iterations, exceeding scan time.

Example (Bad)

FOR i := 0 TO Length - 1 DO (* Length could be 999999 *) Process(Buffer[i]); END_FOR;

Fix

IF Length > MAX_BUFFER THEN Length := MAX_BUFFER; END_IF; FOR i := 0 TO Length - 1 DO Process(Buffer[i]); END_FOR;
LS202 AST Hardcoded Safety Limits Medium

Detects magic numbers used for safety-critical comparisons. Hardcoded values are hard to maintain and audit.

Example (Bad)

IF Pressure > 850 THEN (* Magic number! *) EmergencyRelief := TRUE; END_IF;

Fix

VAR CONSTANT MAX_PRESSURE : INT := 850; END_VAR IF Pressure > MAX_PRESSURE THEN EmergencyRelief := TRUE; END_IF;
LS208 AST Missing Rate-of-Change Limit Medium

Detects setpoints that can change instantly without ramp limiting. Rapid changes can cause equipment damage, thermal shock, or process upsets.

Example (Bad)

Temperature := NewSetpoint; (* Instant change! *)

Fix

IF NewSetpoint > Temperature THEN Temperature := MIN(Temperature + RAMP_RATE, NewSetpoint); ELSIF NewSetpoint < Temperature THEN Temperature := MAX(Temperature - RAMP_RATE, NewSetpoint); END_IF;

Process Control

LS006 AST Missing Watchdog Refresh Critical

Detects watchdog timers that are declared but never refreshed/kicked. If the watchdog expires, the PLC may reset unexpectedly, causing process disruption.

Example (Bad)

VAR WatchdogTimer : TON; (* Never kicked! *) END_VAR

Fix

WatchdogTimer(IN := TRUE, PT := T#100MS); IF WatchdogTimer.Q THEN WatchdogTimer(IN := FALSE); (* Reset to kick *) END_IF;
LS109 AST PID Without Anti-Windup High

Detects PID controllers without integrator anti-windup protection. The integral term can accumulate unbounded, causing large overshoots when the process recovers.

Example (Bad)

Integral := Integral + Error * Ki; (* No limit! *) Output := Kp * Error + Integral;

Fix

Integral := Integral + Error * Ki; IF Integral > I_MAX THEN Integral := I_MAX; END_IF; IF Integral < I_MIN THEN Integral := I_MIN; END_IF; Output := Kp * Error + Integral;
LS206 AST Missing Process Timeout Medium

Detects batch or sequence operations without a maximum duration timeout. A hung process step could run indefinitely.

Example (Bad)

BatchStep := FILLING; WHILE Level < Target DO (* No max time! *) FillValve := TRUE; END_WHILE;

Fix

StepTimer(IN := BatchStep = FILLING, PT := T#10M); IF Level >= Target OR StepTimer.Q THEN FillValve := FALSE; IF StepTimer.Q THEN FaultCode := TIMEOUT; END_IF; END_IF;
LS201 AST Missing Sensor Fault Handling Medium

Detects sensor inputs that are used without range checking or fault detection. Bad sensor readings can cause incorrect control actions.

Example (Bad)

Level := LevelSensor; (* No fault check! *) IF Level > 100 THEN Valve := FALSE; END_IF;

Fix

IF LevelSensor < 0 OR LevelSensor > 200 THEN SensorFault := TRUE; ELSE Level := LevelSensor; END_IF;
LS205 AST Alarm Suppression Medium

Detects alarms that are disabled or suppressed without an automatic timeout to re-enable them. Suppressed alarms can mask real problems.

Example (Bad)

AlarmEnable := FALSE; (* Alarm disabled forever! *)

Fix

AlarmEnable := FALSE; SuppressTimer(IN := TRUE, PT := T#5M); IF SuppressTimer.Q THEN AlarmEnable := TRUE; (* Auto re-enable *) END_IF;

Safety Interlocks

LS009 CROSS-FILE Missing Interlock Critical

Detects programs that read inputs from other programs without proper interlock variables to coordinate execution.

LS011 AST Safety Bypass via Maintenance Mode Critical

Detects when maintenance or service modes can disable safety functions without proper authorization, timeout, or logging.

Example (Bad)

IF MaintenanceKey THEN SafetyDisabled := TRUE; (* No auth, no timeout! *) END_IF;

Fix

IF MaintenanceKey AND AccessLevel >= MAINTENANCE THEN SafetyDisabled := TRUE; MaintTimer(IN:=TRUE, PT:=T#30M); (* Auto-timeout *) LogEvent('SAFETY_BYPASSED', CurrentUser); END_IF; IF MaintTimer.Q THEN SafetyDisabled := FALSE; END_IF;
LS012 AST Unguarded Force Override Critical

Detects outputs that can be forced/overridden without timeout protection or audit logging. Forced outputs can bypass all safety logic.

Example (Bad)

IF ForceCmd THEN Actuator := ForceValue; (* No timeout, no log! *) END_IF;

Fix

IF ForceCmd AND AccessLevel >= ENGINEER THEN Actuator := ForceValue; ForceTimer(IN:=TRUE, PT:=T#5M); LogEvent('OUTPUT_FORCED', Actuator, ForceValue); END_IF; IF ForceTimer.Q OR NOT ForceCmd THEN (* Return to normal control *) END_IF;
LS102 AST Race Condition High

Detects when the same variable is modified in multiple independent IF blocks, which can cause unpredictable behavior depending on scan timing.

Example (Bad)

IF SensorA THEN Forward := TRUE; END_IF; IF SensorB THEN Forward := FALSE; (* Race with above! *) END_IF;

Fix

IF SensorA AND NOT SensorB THEN Forward := TRUE; ELSIF SensorB THEN Forward := FALSE; END_IF;
LS115 AST Single Point of Failure High

Detects critical safety functions that rely on a single sensor or input without redundancy. A sensor failure could cause unsafe operation.

Example (Bad)

IF PressureSensor > MAX THEN (* Single sensor! *) EmergencyRelief := TRUE; END_IF;

Fix

IF PressureSensor_A > MAX OR PressureSensor_B > MAX THEN EmergencyRelief := TRUE; (* Redundant sensors *) END_IF;

Communication & Monitoring

LS007 AST Unvalidated Remote Command Critical

Detects commands received from network/HMI that are used directly without validation or authorization checks. Malicious commands could compromise safety.

Example (Bad)

Motor := RemoteCmd; (* Direct use of remote input! *)

Fix

IF RemoteCmd_Valid AND Authorized AND SafetyOK THEN Motor := RemoteCmd; END_IF;
LS110 AST Missing Heartbeat Check High

Detects remote connections that are assumed alive without heartbeat or timeout verification. A lost connection could leave the system in an unsafe state.

Example (Bad)

IF SCADA_Connected THEN (* Status never verified! *) Output := SCADA_Setpoint; END_IF;

Fix

IF NOW - LastHeartbeat > T#5S THEN CommFault := TRUE; Output := SafeDefault; ELSIF SCADA_Connected THEN Output := SCADA_Setpoint; END_IF;
LS008 AST Recursive Function Call Critical

Detects direct and indirect recursive function calls. PLCs have fixed stack sizes — recursion will cause stack overflow and controller crash. Checks both direct self-calls and call graph cycles.

Example (Bad)

FUNCTION Factorial : INT VAR_INPUT n : INT; END_VAR IF n <= 1 THEN Factorial := 1; ELSE Factorial := n * Factorial(n - 1); (* Stack overflow! *) END_IF; END_FUNCTION

Fix

FUNCTION Factorial : INT VAR_INPUT n : INT; END_VAR VAR result : INT := 1; i : INT; END_VAR FOR i := 2 TO n DO result := result * i; END_FOR; Factorial := result; END_FUNCTION

Security

LS010 AST Hard-Coded Credentials Critical

Detects passwords, API keys, or authentication tokens embedded directly in PLC code. Attackers can extract these from compiled programs or project files.

Example (Bad)

IF Password = 'admin123' THEN (* Hard-coded! *) AccessGranted := TRUE; END_IF;

Fix

(* Use secure credential storage *) IF ValidateCredential(Password, StoredHash) THEN AccessGranted := TRUE; END_IF;
LS111 AST Privilege Escalation High

Detects protected values (calibration, limits, passwords) being modified without checking user access level. Operators could modify supervisor-only settings.

Example (Bad)

CalibrationOffset := HMI_Input; (* No access check! *)

Fix

IF AccessLevel >= SUPERVISOR THEN CalibrationOffset := HMI_Input; END_IF;
LS207 AST Audit Trail Gap Medium

Detects critical operations (E-stop, setpoint changes, mode changes) that are not logged to an audit trail. Required for regulatory compliance and incident investigation.

Example (Bad)

Setpoint := NewValue; (* Change not logged! *)

Fix

Setpoint := NewValue; LogEvent( Timestamp := NOW, User := CurrentUser, Action := 'SETPOINT_CHANGE', OldValue := OldSetpoint, NewValue := Setpoint );

Code Quality

LS203 AST Unreachable Code Medium

Detects code that can never execute due to always-true/false conditions or missing state transitions. May indicate logic errors.

Example (Bad)

IF FALSE THEN (* This code never runs *) Motor := TRUE; END_IF;
LS204 AST Output Never Assigned Medium

Detects VAR_OUTPUT variables that are declared but never assigned a value. The output will have an undefined state.

Example (Bad)

VAR_OUTPUT Status : INT; (* Never assigned! *) END_VAR

Cross-File Analysis

LS116 CROSS-FILE Shared Output Conflict High

Detects when multiple programs write to the same output variable. This causes unpredictable behavior as one program overrides another.

LS117 CROSS-FILE Circular Dependency High

Detects programs that depend on each other's outputs, creating a circular reference that makes execution order matter unpredictably.