@@ -15,7 +15,7 @@ import semmle.code.cpp.controlflow.IRGuards
1515
1616/**
1717 * Functions whose operations should never be considered a
18- * source of a dangerous leap year operation.
18+ * source or sink of a dangerous leap year operation.
1919 */
2020class IgnorableFunction extends Function {
2121 IgnorableFunction ( ) {
@@ -33,6 +33,18 @@ class IgnorableFunction extends Function {
3333 or
3434 // Windows APIs that do time conversions
3535 this .getName ( ) .matches ( "%SpecificLocalTimeToSystemTime%" )
36+ or
37+ // postgres function for diffing timestamps, date for leap year
38+ // is not applicable.
39+ this .getName ( ) .toLowerCase ( ) .matches ( "%timestamp%age%" )
40+ or
41+ // Reading byte streams often involves operations of some base, but that's
42+ // not a real source of leap year issues.
43+ this .getName ( ) .toLowerCase ( ) .matches ( "%read%bytes%" )
44+ or
45+ // A postgres function for local time conversions
46+ // conversion operations (from one time structure to another) are generally ignorable
47+ this .getName ( ) = "localsub"
3648 }
3749}
3850
@@ -83,15 +95,32 @@ class IgnorableCharLiteralArithmetic extends IgnorableOperation {
8395/**
8496 * Constants often used in date conversions (from one date data type to another)
8597 * Numerous examples exist, like 1900 or 2000 that convert years from one
86- * representation to another. All examples found tend to be 100 or greater,
87- * so just using this as a heuristic for detecting such conversion constants.
98+ * representation to another.
8899 * Also '0' is sometimes observed as an atoi style conversion.
89100 */
90101bindingset [ c]
91102predicate isLikelyConversionConstant ( int c ) {
92- exists ( int i | i = c .abs ( ) | i >= 100 )
93- or
94- c = 0
103+ exists ( int i | i = c .abs ( ) |
104+ //| i >= 100)
105+ i = 146097 or // days in 400-year Gregorian cycle
106+ i = 36524 or // days in 100-year Gregorian subcycle
107+ i = 1461 or // days in 4-year cycle (incl. 1 leap)
108+ i = 32044 or // Fliegel–van Flandern JDN epoch shift
109+ i = 1721425 or // JDN of 0001‑01‑01 (Gregorian)
110+ i = 1721119 or // alt epoch offset
111+ i = 2400000 or // MJD → JDN conversion
112+ i = 2400001 or // alt MJD → JDN conversion
113+ i = 2141 or // fixed‑point month/day extraction
114+ i = 2000 or // observed in some conversions
115+ i = 65536 or // observed in some conversions
116+ i = 7834 or // observed in some conversions
117+ i = 256 or // observed in some conversions
118+ i = 1900 or // struct tm base‑year offset; harmless
119+ i = 1899 or // Observed in uses with 1900 to address off by one scenarios
120+ i = 292275056 or // qdatetime.h Qt Core year range first year constant
121+ i = 292278994 or // qdatetime.h Qt Core year range last year constant
122+ i = 0
123+ )
95124}
96125
97126/**
@@ -112,7 +141,35 @@ class IgnorableConstantArithmetic extends IgnorableOperation {
112141}
113142
114143// If a unary minus assume it is some sort of conversion
115- class IgnorableUnaryMinus extends IgnorableOperation instanceof UnaryMinusExpr { }
144+ class IgnorableUnaryMinus extends IgnorableOperation {
145+ IgnorableUnaryMinus ( ) {
146+ this instanceof UnaryMinusExpr
147+ or
148+ this .( Operation ) .getAnOperand ( ) instanceof UnaryMinusExpr
149+ }
150+ }
151+
152+ class OperationAsArgToIgnorableFunction extends IgnorableOperation {
153+ OperationAsArgToIgnorableFunction ( ) {
154+ exists ( Call c |
155+ c .getAnArgument ( ) .getAChild * ( ) = this and
156+ c .getTarget ( ) instanceof IgnorableFunction
157+ )
158+ }
159+ }
160+
161+ /**
162+ * Literal OP literal means the result is constant/known
163+ * and the operation is basically ignorable (it's not a real operation but
164+ * probably one visual simplicity what it means).
165+ */
166+ class ConstantBinaryArithmeticOperation extends IgnorableOperation {
167+ ConstantBinaryArithmeticOperation ( ) {
168+ this instanceof BinaryArithmeticOperation and
169+ this .( BinaryArithmeticOperation ) .getLeftOperand ( ) instanceof Literal and
170+ this .( BinaryArithmeticOperation ) .getRightOperand ( ) instanceof Literal
171+ }
172+ }
116173
117174class IgnorableBinaryBitwiseOperation extends IgnorableOperation instanceof BinaryBitwiseOperation {
118175}
@@ -132,12 +189,31 @@ class IgnorablePointerOrCharArithmetic extends IgnorableOperation {
132189 this .( BinaryArithmeticOperation ) .getAnOperand ( ) .getUnspecifiedType ( ) instanceof PointerType
133190 or
134191 this .( BinaryArithmeticOperation ) .getAnOperand ( ) .getUnspecifiedType ( ) instanceof CharType
192+ or
193+ // Operations on calls to functions that accept char or char*
194+ this .( BinaryArithmeticOperation )
195+ .getAnOperand ( )
196+ .( Call )
197+ .getAnArgument ( )
198+ .getUnspecifiedType ( )
199+ .stripType ( ) instanceof CharType
200+ or
201+ // Operations on calls to functions named like "strlen", "wcslen", etc
202+ // NOTE: workaround for cases where the wchar_t type is not a char, but an unsigned short
203+ // unclear if there is a best way to filter cases like these out based on type info.
204+ this .( BinaryArithmeticOperation ) .getAnOperand ( ) .( Call ) .getTarget ( ) .getName ( ) .matches ( "%len%" )
135205 )
136206 or
137207 exists ( AssignArithmeticOperation a | a .getRValue ( ) = this |
138208 a .getAnOperand ( ) .getUnspecifiedType ( ) instanceof PointerType
139209 or
140210 a .getAnOperand ( ) .getUnspecifiedType ( ) instanceof CharType
211+ or
212+ // Operations on calls to functions that accept char or char*
213+ a .getAnOperand ( ) .( Call ) .getAnArgument ( ) .getUnspecifiedType ( ) .stripType ( ) instanceof CharType
214+ or
215+ // Operations on calls to functions named like "strlen", "wcslen", etc
216+ this .( BinaryArithmeticOperation ) .getAnOperand ( ) .( Call ) .getTarget ( ) .getName ( ) .matches ( "%len%" )
141217 )
142218 }
143219}
@@ -161,25 +237,6 @@ predicate isOperationSourceCandidate(Expr e) {
161237 )
162238}
163239
164- /**
165- * A Dataflow that identifies flows from an Operation (addition, subtraction, etc) to some ignorable operation (bitwise operations for example) that disqualify it
166- */
167- module OperationSourceCandidateToIgnorableOperationConfig implements DataFlow:: ConfigSig {
168- predicate isSource ( DataFlow:: Node n ) { isOperationSourceCandidate ( n .asExpr ( ) ) }
169-
170- predicate isSink ( DataFlow:: Node n ) { n .asExpr ( ) instanceof IgnorableOperation }
171-
172- // looking for sources and sinks in the same function
173- DataFlow:: FlowFeature getAFeature ( ) {
174- result instanceof DataFlow:: FeatureEqualSourceSinkCallContext
175- }
176-
177- predicate isBarrierOut ( DataFlow:: Node n ) { isSink ( n ) }
178- }
179-
180- module OperationSourceCandidateToIgnorableOperationFlow =
181- TaintTracking:: Global< OperationSourceCandidateToIgnorableOperationConfig > ;
182-
183240/**
184241 * A dataflow that tracks an ignorable operation (eg. bitwise op) to a operation source, so we may disqualify it.
185242 */
@@ -210,10 +267,10 @@ module IgnorableOperationToOperationSourceCandidateFlow =
210267class OperationSource extends Expr {
211268 OperationSource ( ) {
212269 isOperationSourceCandidate ( this ) and
213- not exists ( OperationSourceCandidateToIgnorableOperationFlow :: PathNode src |
214- src . getNode ( ) . asExpr ( ) = this and
215- src . isSource ( )
216- ) and
270+ // If the candidate came from an ignorable operation, ignore the candidate
271+ // NOTE: we cannot easily flow the candidate to an ignorable operation as that can
272+ // be tricky in practice, e.g., a mod operation on a year would be part of a leap year check
273+ // but a mod operation ending in a year is more indicative of something to ignore (a conversion)
217274 not exists ( IgnorableOperationToOperationSourceCandidateFlow:: PathNode sink |
218275 sink .getNode ( ) .asExpr ( ) = this and
219276 sink .isSink ( )
@@ -223,28 +280,37 @@ class OperationSource extends Expr {
223280
224281class YearFieldAssignmentNode extends DataFlow:: Node {
225282 YearFieldAssignmentNode ( ) {
226- this .asExpr ( ) instanceof YearFieldAssignment
227- // or
228- // this.asDefiningArgument() instanceof YearFieldAccess
229- // or
230- // // TODO: is there a better way to do this?
231- // // i.e., without having to be cognizant of the addressof
232- // // occurring, especially if this occurs on a dataflow
233- // exists(AddressOfExpr aoe |
234- // aoe = this.asDefiningArgument() and
235- // aoe.getOperand() instanceof YearFieldAccess
236- // )
283+ not this .getEnclosingCallable ( ) .getUnderlyingCallable ( ) instanceof IgnorableFunction and
284+ (
285+ this .asExpr ( ) instanceof YearFieldAssignment or
286+ this .asDefiningArgument ( ) instanceof YearFieldOutArgAssignment
287+ )
237288 }
238289}
239290
240291/**
241292 * An assignment to a year field, which will be a sink
242- * NOTE: could not make this abstract, it was binding to all expressions
243293 */
244294abstract class YearFieldAssignment extends Expr {
245295 abstract YearFieldAccess getYearFieldAccess ( ) ;
246296}
247297
298+ class YearFieldOutArgAssignment extends YearFieldAssignment {
299+ YearFieldAccess access ;
300+
301+ YearFieldOutArgAssignment ( ) {
302+ exists ( Call c | c .getAnArgument ( ) = access and this = access )
303+ or
304+ exists ( Call c , AddressOfExpr aoe |
305+ c .getAnArgument ( ) = aoe and
306+ aoe .getOperand ( ) = access and
307+ this = aoe
308+ )
309+ }
310+
311+ override YearFieldAccess getYearFieldAccess ( ) { result = access }
312+ }
313+
248314/**
249315 * An assignment to x ie `x = a`.
250316 */
@@ -298,7 +364,6 @@ module OperationToYearAssignmentConfig implements DataFlow::ConfigSig {
298364 predicate isBarrierIn ( DataFlow:: Node n ) { isSource ( n ) }
299365
300366 predicate isBarrierOut ( DataFlow:: Node n ) { isSink ( n ) }
301- // DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
302367}
303368
304369module OperationToYearAssignmentFlow = TaintTracking:: Global< OperationToYearAssignmentConfig > ;
@@ -335,11 +400,6 @@ module YearFieldAccessToLeapYearCheckConfig implements DataFlow::ConfigSig {
335400 node2 .asExpr ( ) = node1 .asExpr ( ) .( YearFieldAccess ) .getQualifier ( )
336401 }
337402
338- // Use this to make the sink for leap year check intra-proc only
339- // i.e., the sink must be in the same scope (function) as the source.
340- // DataFlow::FlowFeature getAFeature() {
341- // result instanceof DataFlow::FeatureEqualSourceSinkCallContext
342- // }
343403 /**
344404 * Enforcing the check must occur in the same call context as the source,
345405 * i.e., do not return from the source function and check in a caller.
@@ -357,20 +417,26 @@ predicate isYearModifiedWithCheck(YearFieldAccess fa) {
357417 src .getNode ( ) .asExpr ( ) .( YearFieldAssignment ) .getYearFieldAccess ( ) = fa
358418 )
359419 or
360- isUsedInFeb29Check ( fa )
361- }
362-
363- /**
364- * If there is a flow from a date struct year field access/assignment to a Feb 29 check
365- */
366- predicate isUsedInFeb29Check ( YearFieldAccess fa ) {
367- exists ( DateFebruary29Check check |
368- DataFlow:: localExprFlow ( fa .getQualifier ( ) , check .getDateQualifier ( ) )
369- or
370- DataFlow:: localExprFlow ( check .getDateQualifier ( ) , fa .getQualifier ( ) )
420+ // If the time flows to a time conversion whose value/result is checked,
421+ // assume the leap year is being handled.
422+ exists ( TimeStructToCheckedTimeConversionFlow:: PathNode timeQualSrc |
423+ timeQualSrc .isSource ( ) and
424+ timeQualSrc .getNode ( ) .asExpr ( ) = fa .getQualifier ( )
371425 )
426+ // or
427+ // isUsedInFeb29Check(fa)
372428}
373429
430+ // /**
431+ // * If there is a flow from a date struct year field access/assignment to a Feb 29 check
432+ // */
433+ // predicate isUsedInFeb29Check(YearFieldAccess fa) {
434+ // exists(DateFebruary29Check check |
435+ // DataFlow::localExprFlow(fa.getQualifier(), check.getDateQualifier())
436+ // or
437+ // DataFlow::localExprFlow(check.getDateQualifier(), fa.getQualifier())
438+ // )
439+ // }
374440/**
375441 * An expression which checks the value of a Month field `a->month == 1`.
376442 */
@@ -400,12 +466,66 @@ predicate isControlledByMonthEqualityCheckNonFebruary(Expr e) {
400466 )
401467}
402468
469+ module TimeStructToCheckedTimeConversionConfig implements DataFlow:: StateConfigSig {
470+ class FlowState = boolean ;
471+
472+ predicate isSource ( DataFlow:: Node source , FlowState state ) {
473+ exists ( YearFieldAccess yfa | source .asExpr ( ) = yfa .getQualifier ( ) ) and
474+ state = false
475+ }
476+
477+ predicate isSink ( DataFlow:: Node sink , FlowState state ) {
478+ state = true and
479+ exists ( IfStmt ifs | ifs .getCondition ( ) .getAChild * ( ) = sink .asExpr ( ) )
480+ }
481+
482+ predicate isAdditionalFlowStep (
483+ DataFlow:: Node node1 , FlowState state1 , DataFlow:: Node node2 , FlowState state2
484+ ) {
485+ state1 in [ true , false ] and
486+ state2 = true and
487+ exists ( Call c |
488+ c .getTarget ( ) instanceof TimeConversionFunction and
489+ c .getAnArgument ( ) .getAChild * ( ) = node1 .asExpr ( ) and
490+ node2 .asExpr ( ) = c
491+ )
492+ }
493+
494+ DataFlow:: FlowFeature getAFeature ( ) { result instanceof DataFlow:: FeatureHasSourceCallContext }
495+ }
496+
497+ module TimeStructToCheckedTimeConversionFlow =
498+ DataFlow:: GlobalWithState< TimeStructToCheckedTimeConversionConfig > ;
499+
403500import OperationToYearAssignmentFlow:: PathGraph
404501
405502from OperationToYearAssignmentFlow:: PathNode src , OperationToYearAssignmentFlow:: PathNode sink
406503where
407504 OperationToYearAssignmentFlow:: flowPath ( src , sink ) and
408505 not isYearModifiedWithCheck ( sink .getNode ( ) .asExpr ( ) .( YearFieldAssignment ) .getYearFieldAccess ( ) ) and
409- not isControlledByMonthEqualityCheckNonFebruary ( sink .getNode ( ) .asExpr ( ) )
506+ not isControlledByMonthEqualityCheckNonFebruary ( sink .getNode ( ) .asExpr ( ) ) and
507+ // TODO: this is an older heuristic that should be reassessed.
508+ // The heuristic stipulates that if a month or day is set to a constant in the local flow up to or after
509+ // a modified year's sink, then assume the user is knowingly handling the conversion correctly.
510+ // There is no interpretation of the assignment of the month/day or consideration for what branch the assignment is on.
511+ // Merely if the assignment of a constant day/month occurs anywhere, the year modification can likely
512+ // be ignored.
513+ not exists ( FieldAccess mfa , AssignExpr ae , YearFieldAccess yfa , int val , Variable var |
514+ mfa instanceof MonthFieldAccess or mfa instanceof DayFieldAccess
515+ |
516+ yfa = sink .getNode ( ) .asExpr ( ) .( YearFieldAssignment ) .getYearFieldAccess ( ) and
517+ var .getAnAccess ( ) = yfa .getQualifier ( ) and
518+ mfa .getQualifier ( ) = var .getAnAccess ( ) and
519+ mfa .isModified ( ) and
520+ (
521+ mfa .getBasicBlock ( ) = yfa .getBasicBlock ( ) .getASuccessor * ( ) or
522+ yfa .getBasicBlock ( ) = mfa .getBasicBlock ( ) .getASuccessor + ( )
523+ ) and
524+ ae = mfa .getEnclosingElement ( ) and
525+ ae .getAnOperand ( ) .getValue ( ) .toInt ( ) = val and
526+ // If the constant looks like it relates to february, then there still might be an error
527+ // so only look for any other constant.
528+ not val in [ 2 , 28 , 29 ]
529+ )
410530select sink , src , sink ,
411531 "Year field has been modified, but no appropriate check for LeapYear was found."
0 commit comments