270 likes | 408 Views
Static Analysis to Enforce Safe Value Flow in Embedded Control Systems. DSN 2006 Sumant Kowshik, Grigore Rosu , Lui Sha University of Illinois at Urbana-Champaign. Architectural Isolation of Core Functionality. Case Study: Simplex. Non-core. control. Core cont. Run-time monitor.
E N D
Static Analysis to Enforce Safe Value Flow in Embedded Control Systems DSN 2006 Sumant Kowshik, Grigore Rosu, Lui Sha University of Illinois at Urbana-Champaign
Architectural Isolation of Core Functionality Case Study: Simplex Non-core control Core cont. Run-time monitor Core subsystem feedback • Non-core subsystem • Complex and relatively untested • Performance, user-interfaces, optional features • Core subsystem • Simple, well-tested components • Purpose: Safety • Emerging Architectural Principle: Isolation of core functionality from non-core subsystem failures
Architecture Implementation An Implementation of the Simplex Architecture Core feedback feedback Non-core Safe Cont. control SHM Monitor Platform • Single-threaded programs: Simplex and Non-core controller • Core: Safety controller, run-time monitor, device I/O • Shared Platform; Communicate using Shared Memory (SHM)
Value Flow in Control Systems Core feedback feedback Non-core Safe Cont. control SHM Monitor Platform • Complete isolation of core and non-core components is impractical • In general two-way communication between core and non-core • core to non-core -- feedback information • non-core to core – computed high-performance controls
Monitoring Value Errors in Control • Value errors easier to monitor for recoverability in control systems due to the continuous nature of control variables • Example 1: Controller outputs have a finite bounded effect on physical plant [Cunha, Simplex] • Validity cannot be checked; System recoverability can be checked using inbuilt conservative model • Example 2: Feedback from sensor can have intermittent errors • State estimator uses estimated position as a validation check • Core component should onlyusenon-core data (run-time monitor); should notdependon it [Ding and Sha]
corrupt hogging • Resource Usage: Guaranteeing a set of logical system resources to the core component • System call monitoring; Real-time virtual machines Background: Isolating the Core Component Core feedback feedback Non-core Safe Cont. control SHM Monitor Platform • Memory Safety: Memory errors such as dangling pointers can overwrite and corrupt the core component • Prior Work: Control-C (SAFECode), CCured
Monitoring function Implementation of Value Flow Example: Simplex simplex_ main() { initializeSHM(); lockSHM(); while (1) { readFeedback(F); publishFeedback(F); safetyCtrl = safety_control(F); unlockSHM(); pause(); lockSHM(); output = decision(safetyCtrl, &SHM->non-core, F); sendControl(output); } } SHM Non-core Feedback Feedback Non-core Control Control Output
Implementation Errors I simplex_ main() { initializeSHM(); lockSHM(); while (1) { readFeedback(F); publishFeedback(F); safetyCtrl = safety_control(F); unlockSHM(); pause(); lockSHM(); if (flag) { output = decision(safetyCtrl, &SHM->noncore, F); sendControl(output); } else { sendControl(SHM->noncore); } SHM Feedback Non-core Feedback Non-core Control 1. Unmonitored non-core value: SHM->noncore unmonitored along false path Control Output
Implementation Errors II Example: Simplex simplex_ main() { initializeSHM(); lockSHM(); while (1) { readFeedback(F); publishFeedback(F); safetyCtrl = safety_control(F); unlockSHM(); pause(); lockSHM(); output = decision(safetyCtrl, &SHM->noncore, &SHM->F); sendControl(output); } SHM Feedback Non-core Feedback Non-core Control 2. Hidden Dependencies: SHM->F unknowingly dereferenced Control Output
Implementation Errors III Example: Simplex simplex_ main() { initializeSHM(); lockSHM(); while (1) { readFeedback(F); publishFeedback(F); ref = getReferencePoint(); safetyCtrl = safety_control(F, ref); unlockSHM(); pause(); lockSHM(); output = decision(safetyCtrl, &SHM->noncore ,F); sendControl(output); } } SHM Non-core Feedback Feedback Non-core Control Reference 3. Wrong Assumptions: E.g. Reference will not be corrupted by non-core component Control Output
Assumptions and Goals Goals Make communication with non-core components explicit Enforce that all non-core data is run-time monitored before use in critical data by the core component • Required Assumptions • The run-time monitor for the non-core values is correct! • Communication through shared memory only • Performance critical systems • More challenging scenario
Contributions and Limitations • Annotation language/rigor and accompanying analysis • + Simple, local annotations on C • + Expressive language to program embedded control systems in spite of the usage restrictions • + Purely static analysis to precisely find all unmonitored values from non-core components • Also identifies errors where unmonitored accesses can corrupt critical values even in long-running systems • Few false positives in errors due to control dependency • Wrong annotations can lead to undetected errors
Approach: Monitoring Functions • For all shared variables, x • Noncore.read(x) safe • Noncore.write(x) safe • Core.read(x) warning (error, if x propagates to critical data) • Core.write(x) safe Core.monitoringRead(x) safe, within monitoring “zone” if x is declared to be safe to read in it • Monitoring functions can be annotated so that shared variables are safe to read in these functions (and their callees)
Monitoring Functions: assume Annotation simplex_ main() { initializeSHM(); while (1) { readFeedback(F); publishFeedback(F); safetyCtrl = safety_control(F); unlockSHM(); pause(); lockSHM(); output = decision(safetyCtrl, &SHM->non-core, &SHM->F); sendControl(output); } } simplex_ main() { initializeSHM(); while (1) { readFeedback(F); publishFeedback(F); safetyCtrl = safety_control(F); unlockSHM(); pause(); lockSHM(); output = decision(safetyCtrl, &SHM->non-core, &SHM->F); sendControl(output); } } float decision (float safeControl, SHMData *noncore, Feedback *F) { if (checkSafety(F, noncore)) return noncore->control; else return safeControl; } /***SafeFlowAnnot: assume(safe(noncore, 0, sizeof(SHMData))) ***/ Use the assume annotation to specify the safely accessible shared memory locations Simple, local annotation
assert Annotation simplex_ main() { initializeSHM(size); while (1) { readFeedback(F); publishFeedback(F); safetyCtrl = safety_control(F); unlockSHM(); pause(); lockSHM(); output = decision(safetyCtrl, &SHM->non-core, &SHM->F); sendControl(output); } } /***SafeFlowAnnot: assert(safe(output)) ***/ Assert annotation placed before critical data such as control outputs sent to the plant or argument of system calls such as kill
Restrictions on Shared Memory Usage • Shared memory not deallocated until end of main() • Pointers to shared memory • Cannot be cast to incompatible types • Address cannot be taken • Arrays in shared memory • Indices within array bounds • Loop bounds affine w.r.t size of array and loop index variables or vice versa No dangling pointers to shared memory Enforce shared memory pointers not aliased through memory
/***SafeFlow Annotation assume(shminit); ***/ Initialization Function Shared Memory Initialization initializeSHM(size_t size) { void *shmStart; /* Initialize shared memory */ shmid = shmget(SHMKEY, size, flags); shmStart = shmat(shmid, 0, 0); feedback = (SHMData *) shmStart; noncore = feedback + 1; } simplex_ main() { initializeSHM(); while (1) { readFeedback(F); publishFeedback(F); safetyCtrl = safety_control(F); unlockSHM(); pause(); lockSHM(); output = decision(safetyCtrl, &SHM->non-core, &SHM->F); sendControl(output); } } simplex_ main() { initializeSHM(size); while (1) { readFeedback(F); publishFeedback(F); safetyCtrl = safety_control(F); unlockSHM(); pause(); lockSHM(); output = decision(safetyCtrl, &SHM->non-core, &SHM->F); sendControl(output); } } /***SafeFlow Annotation: post-condition assume(non-core(feedback)) assume(non-core(noncore)) assume(size(feedback) = sizeof(SHMData)) assume(size(noncore) = sizeof(SHMData)) ***/ • Language restrictions not applied to initialization function • Unix shared memory initialization is untyped (needs downcasts) • Arrays in shared memory need to be made explicit
Simplex_main() { … output = decision(safetyCtrl, &SHM->non-core, &SHM->F); } simplex_ main() { … sendControl(output); … } /***SafeFlowAnnot: assert(safe(output)) ***/ Step #4 Propagates to assert violation SAFEFLOW Analysis Algorithm At Work float decision (float safeControl, SHMData *noncore, Feedback *F) { if (checkSafety(F, noncore)) return noncore->control; else return safeControl; } Step #1 SHMPtrs(decision/checkSafety) noncore -- <noncore, 0> F -- <feedback, 0> /***SafeFlowAnnot: assume(safe(noncore, 0, sizeof(SHMData))) ***/ Step #2 SafeSHMPtrs(decision/checkSafety) noncore – <noncore, 0, size> Warning reported Step #3 F is unsafe incheckSafety Error reported
Evaluation Implementation: As a sequence of analyses on LLVM byte code • Annotation burden • Few local annotations • Majority of annotations (15/22, 15/23 and 9/11) are initializing function annotations • Source changes only for separation of monitoring function -- Diff changes don’t reflect actual effort
Evaluation II • Found two error values propagated in generic eSimplex implementation; one in IP; two in double IP early impl. • Dependency between core and non-core controller -- caused due to one of three different assumptions (or oversight!) • No data races with the unreliable controller • Non-core controller is encapsulated to write onto fixed shm field • Unmonitored read does not affect a critical value • All exploitable to violate safety requirements • Errors contained 2 false positives in IP; 2 in double IP; 6 in generic • Main reason: Control dependency on variables such as subscribedController • False positive test --Use the value flow chain and enforce that each link in the chain is safe i.e. the value is not used in the computation of a critical value • BUT, good software engineering to separate these values out of core component • Warnings are precise (modulo correct annotations) – due to language restrictions
Related Work Applicability Prior Work Heavy-weight type system for confidentiality, integrity of program vars Secure Information Flow E.g. JIF, Volpano et al Best-effort (unsound) bug detection Metal Specifications Type qualifiers useful in propagation of attributes; No monitoring; False positives Approach practical for embedded systems CQual Run-time detection using tags taintPerl • In contrast, our analysis exploits domain information to (1) have few false positives; (2) use light-weight annotations; and (3) incur no run-time overhead
Safe inter-component interaction Property to be verified Synch. Bugs; Compatible interfaces The difficult bugs Run-time monitoring of non-core values What we verify? Summary • Static analysis is a very attractive tool for embedded system architecture conformance such as core functionality isolation • BUT, pick an analyzable and specifiable property • Simple annotations provides rigor to explicitly identify unsafe value flow to core • The errors caught from such an approach are subtle, unexpected, and potentially catastrophic Restricted shared memory pointers + annotations Restrictions Imposed
Future Directions • Extend to systems, which contain multiple criticality levels • Annotations can specify more properties about monitors • Value flow through other channels, particularly the environment – e.g. files, env variables • Extend analyses to other architectural properties • E.g. enforcing that all feedback values are Kalman filtered • Annotation language and analysis can be reused: filtering functions and annotations with corresponding predicates
Error #2 Programmer unknowingly accesses non-core data OR Programmer’s assumptions are wrong AND Non-core data affects critical data in core component Sources of Implementation Errors: Summary Source: Four laboratory control systems Error #1 Var not monitored along some path Non-core data Monitored shared vars Feedback Non-core Control • Unmonitored shared vars • Why not? • Programmer simply missed it! • Programmer assumes this part of shm does not contain non-core value • Programmer assumes that var does not affect critical data in the core component Reference
SAFEFLOW Analysis Algorithm • Find shared memory initializations and all shared memory pointers in each function – SHMPtrs(F) • Discover initializing functions • Interprocedurally propagate the shared memory pointers and the <SHM, offset> that they refer to • In each function Enforce language restrictions on shared memory pointers • Detect unsafe type-casts or array indexing Process assume annotations and identify unsafe shared memory pointer dereferences [Does <SHM, offset> belong to SafeSet<SHM, offset>?] • Propagate unsafe values and discover any assert violations • Use value flow graphs (similar to ESP or type-qualifier inference in CQual) • Implementation-- On LLVM byte code as a series interprocedural passes: • ValueFlow Analysis followed by SafeFlow analyses
Alternate Solution: Safe SHM Library Goal: A safe set of programming primitives for value propagation to ease programmer effort Pros: No annotations Cons: More restructuring; Initialize shared memory size SHMStruct *theSHM; float feedback = readFeedback(); theSHM = (SHMStruct *) libshminit(sizeof(SHMStruct)); register_monitor((void *) theSHM, &valMonitor, sizeof(theSHM->fld0)); double ctrl; if (readSHMVal((void *) the SHM, sizeof(theSHM->fld0), &ctrl, (void *) &feedback)) { // ctrl is safe } else { ctrl = SAFE_VALUE; } sendControl(ctrl); Register int monitor for offset Read value in shm at offset