Skip to content

[Security] iobCalcBilinear() — division by zero when dia=0 silently returns IOB=0 (CWE-369) #1480

@dom-omg

Description

@dom-omg

Summary

A missing input validation in iobCalcBilinear() causes Insulin-On-Board (IOB) to be
silently reported as 0 when profile.dia = 0. No exception is thrown. No log entry
is written. The APS loop receives IOB=0 and may authorize a correction bolus on top
of already-active insulin.

Affected File

lib/iob/calculate.jsiobCalcBilinear(), lines 45–56

Root Cause

var timeScalar   = default_dia / dia;     // dia=0 → Infinity (IEEE 754)                                                         
var activityPeak = 2 / (dia * 60);       // dia=0 → Infinity            
var scaled_minsAgo = timeScalar * minsAgo; // Infinity * N → Infinity                                                            
                                                                                                                                 
if (scaled_minsAgo < peak) { ... }       // Infinity < 75  → FALSE always                                                        
else if (scaled_minsAgo < end) { ... }   // Infinity < 180 → FALSE always                                                        
                                                                                                                                 
// Neither branch executes → returns { activityContrib: 0, iobContrib: 0 }                                                       
                                                                                                                                 
JavaScript does not throw on division by zero  it returns Infinity. Both branch                                                 
conditions evaluate to false, so the function returns zero for all values of                                                     
minsAgo, regardless of how much insulin was dosed.                                                                               
                                                                                                                                 
Reproduction                                                                                                                     
                                                                                                                                 
const iobCalc = require('./lib/iob/calculate');                                                                                  
 
const treatment = { insulin: 1.0, date: new Date(Date.now() - 30*60*1000) };                                                     
const profile   = { curve: 'bilinear' };                                                                                       
                                                                                                                                 
// Normal dia=4: IOB correctly calculated                                                                                        
console.log(iobCalc(treatment, new Date(), 'bilinear', 4.0, 75, profile));                                                       
// → { activityContrib: ~0.011, iobContrib: ~0.95 }                                                                              
                                                                                                                                 
// dia=0: IOB silently zeroed                                                                                                    
console.log(iobCalc(treatment, new Date(), 'bilinear', 0, 75, profile));                                                         
// → { activityContrib: 0, iobContrib: 0 }   ← WRONG, 1u still active                                                            
                                                                                                                                 
Secondary Issue                                                                                                                  
                                                                                                                                 
In iobCalcExponential(): if profile.curve is not "rapid-acting" or                                                               
"ultra-rapid", console.error() fires but execution continues with the                                                          
original peak parameter unchecked. If the caller passed peak=undefined,                                                          
NaN arithmetic follows silently.                                                                                                 
                                                                                                                                 
Impact                                                                                                                           
                                                                                                                                 
If profile.dia is set to 0 (invalid, but not validated at function entry),                                                       
the IOB calculation returns 0 for the entire duration of any active bolus.
The loop's decision logic (if reportedIOB < correction → authorize new dose)                                                     
will interpret this as "no insulin active" and may authorize a repeat correction                                                 
bolus, increasing hypoglycemia risk.                                                                                             
                                                                                                                                 
Formal Verification                                                                                                              
                                                                                                                               
Four Z3 SMT theorems were verified against a mathematical model of this function:                                                
 
- OH1 SAT: confirmed  IOB=0 when insulin>0, minsAgo∈(0,180), dia=0                                                              
- OH2 SAT: confirmed  repeat bolus authorized despite active IOB                                                              
- OH3 UNSAT: confirmed  dia > 0 guard at entry fully eliminates the bug                                                         
- OH4 SAT: confirmed  invalid profile.curve causes silent NaN propagation                                                       
                                                                                                                                 
Suggested Fix                                                                                                                    
                                                                                                                                 
Add an explicit guard at the top of both iobCalcBilinear() and iobCalcExponential():                                             

if (!dia || dia <= 0) {                                                                                                          
    console.error('Invalid DIA value:', dia, '— must be > 0. Check profile settings.');                                          
    return { activityContrib: 0, iobContrib: 0, error: 'INVALID_DIA' };                                                          
}                                                                                                                                
                                                                                                                                 
This is an early-return with a logged error. The function already returns                                                        
{ activityContrib: 0, iobContrib: 0 } in the buggy path  this fix makes
that explicit and logged rather than silent.                                                                                     
                                                                                                                                 
Notes                                                                                                                            
                                                                                                                                 
- This requires dia=0 in the user profile, which is an invalid configuration.                                                    
- No external attacker trigger  this is a safety/reliability issue, not an
exploitable vulnerability in the traditional security sense.                                                                     
- Recommending a CVE request under CWE-369 given the medical device context.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions