Skip to content

Commit e2d3d15

Browse files
committed
Minor comment/description clean up, and added a test case and support for handing mktime and other variants that convert the time automatically without the need for a check if the date is an incorrect leap year date.
1 parent 3ba543e commit e2d3d15

4 files changed

Lines changed: 49 additions & 17 deletions

File tree

cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -528,18 +528,30 @@ class SafeTimeGatheringFunction extends Function {
528528
* This list of APIs should check for the return value to detect problems during the conversion.
529529
*/
530530
class TimeConversionFunction extends Function {
531+
boolean autoConverts;
532+
531533
TimeConversionFunction() {
532-
this.getQualifiedName() =
533-
[
534-
"FileTimeToSystemTime", "SystemTimeToFileTime", "SystemTimeToTzSpecificLocalTime",
535-
"SystemTimeToTzSpecificLocalTimeEx", "TzSpecificLocalTimeToSystemTime",
536-
"TzSpecificLocalTimeToSystemTimeEx", "RtlLocalTimeToSystemTime",
537-
"RtlTimeToSecondsSince1970", "_mkgmtime", "SetSystemTime", "SystemTimeToVariantTime",
538-
"VariantTimeToSystemTime",
539-
// NOTE: mktime will normalize a Feb 29 on a non-leap year to Mar 1 silently,
540-
"mktime", "_mktime32", "_mktime64", "VarUdateFromDate"
541-
]
534+
autoConverts = false and
535+
(
536+
this.getQualifiedName() =
537+
[
538+
"FileTimeToSystemTime", "SystemTimeToFileTime", "SystemTimeToTzSpecificLocalTime",
539+
"SystemTimeToTzSpecificLocalTimeEx", "TzSpecificLocalTimeToSystemTime",
540+
"TzSpecificLocalTimeToSystemTimeEx", "RtlLocalTimeToSystemTime",
541+
"RtlTimeToSecondsSince1970", "_mkgmtime", "SetSystemTime", "SystemTimeToVariantTime",
542+
"VariantTimeToSystemTime", "VarUdateFromDate", "boost::gregorian::date::from_tm"
543+
]
544+
or
545+
this.getQualifiedName().matches("GetDateFormat%")
546+
)
542547
or
543-
this.getQualifiedName().matches("GetDateFormat%")
548+
// NOTE: mktime will normalize a Feb 29 on a non-leap year to Mar 1 silently,
549+
autoConverts = true and
550+
this.getQualifiedName() = ["mktime", "_mktime32", "_mktime64"]
544551
}
552+
553+
/**
554+
* Holds if the function is expected to auto convert a bad leap year date.
555+
*/
556+
predicate isAutoConverting() { autoConverts = true }
545557
}

cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* @name Year field changed using an arithmetic operation without checking for leap year (AntiPattern 1)
3-
* @description A field that represents a year is being modified by an arithmetic operation, but no proper check for leap years can be detected afterwards.
3+
* @description A field that represents a year is being modified by an arithmetic operation, but no proper check for leap years can be detected.
44
* @kind path-problem
55
* @problem.severity warning
66
* @id cpp/microsoft/public/leap-year/unchecked-after-arithmetic-year-modification
@@ -76,6 +76,10 @@ class IgnorableFunction extends Function {
7676
this.getName()
7777
.toLowerCase()
7878
.matches(["%char%to%", "%string%to%", "%from%char%", "%from%string%"])
79+
or
80+
// boost's gregorian.cpp has year manipulations that are checked in complex ways.
81+
// ignore the entire file as a source or sink.
82+
this.getFile().getAbsolutePath().toLowerCase().matches("%boost%gregorian.cpp%")
7983
}
8084
}
8185

@@ -490,11 +494,9 @@ predicate isControlledByMonthEqualityCheckNonFebruary(Expr e) {
490494
}
491495

492496
/**
493-
* Flow from a year field access through a time conversion function
494-
* where the call's result is used to check error. The result must
495-
* be used as a guard for an if or ternary operator. If so,
496-
* assume some sort of error handling is occurring that could be used
497-
* to detect bad dates due to leap year.
497+
* From from a year field access to a time conversion function
498+
* that auto converts feb29 in non-leap year, or through a conversion function that doesn't
499+
* auto convert to a sanity check guard of the result for error conditions.
498500
*/
499501
module YearAssignmentToCheckedTimeConversionConfig implements DataFlow::StateConfigSig {
500502
class FlowState = boolean;
@@ -515,6 +517,13 @@ module YearAssignmentToCheckedTimeConversionConfig implements DataFlow::StateCon
515517
or
516518
exists(Loop l | l.getCondition().getAChild*() = [sink.asExpr(), sink.asIndirectExpr()])
517519
)
520+
or
521+
state in [true, false] and
522+
exists(Call c, TimeConversionFunction f |
523+
f.isAutoConverting() and
524+
c.getTarget() = f and
525+
c.getAnArgument().getAChild*() = [sink.asExpr(), sink.asIndirectExpr()]
526+
)
518527
}
519528

520529
predicate isAdditionalFlowStep(

cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedLeapYearAfterYearModification.expected

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,7 @@ nodes
159159
| test.cpp:1584:2:1584:22 | ... += ... | semmle.label | ... += ... |
160160
| test.cpp:1596:2:1596:22 | ... += ... | semmle.label | ... += ... |
161161
| test.cpp:1609:2:1609:22 | ... += ... | semmle.label | ... += ... |
162+
| test.cpp:1644:2:1644:22 | ... += ... | semmle.label | ... += ... |
163+
| test.cpp:1649:2:1649:22 | ... += ... | semmle.label | ... += ... |
164+
| test.cpp:1678:2:1678:22 | ... += ... | semmle.label | ... += ... |
162165
subpaths

cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,5 +1671,13 @@ void leap_year_check_call_on_conversion3(tm timeinfo, WORD year, WORD month, WOR
16711671
timeinfo.tm_year = year;
16721672
}
16731673

1674+
void assumed_maketime_conversion1(tm timeinfo)
1675+
{
1676+
//the docs of mktime suggest feb29 is handled, and conversion will occur automatically
1677+
//no check required.
1678+
timeinfo.tm_year += 1;
1679+
1680+
mktime(&timeinfo);
1681+
}
16741682

16751683

0 commit comments

Comments
 (0)