Skip to content

Commit acdbb8d

Browse files
committed
Adding FP checks for assignment of a constant safe date regardless of year, similar to what was done for month.
1 parent f8a2f5c commit acdbb8d

3 files changed

Lines changed: 107 additions & 19 deletions

File tree

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

Lines changed: 78 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -712,41 +712,40 @@ class LeapYearGuardCondition extends GuardCondition {
712712
}
713713

714714
/**
715-
* A difficult case to detect is if a year modification is tied to a month modification
716-
* and the month is safe for leap year.
715+
* A difficult case to detect is if a year modification is tied to a month or day modification
716+
* and the month or day is safe for leap year.
717717
* e.g.,
718718
* year++;
719719
* month = 1;
720+
* // alternative: day = 15;
720721
* ... values eventually used in the same time struct
721722
* If this is even more challenging if the struct the values end up in are not
722723
* local (set inter-procedurally).
723-
* This flow flows constants 1-12 (but not 2 as this likely means february) to a month assignment.
724+
* This flow flows constants 1-31 to a month or day assignment.
724725
* It is assumed a user of this flow will check if the month/day source and month/day sink
725726
* are in the same basic blocks as a year modification source and a year modification sink.
726727
* It is also assumed a user will check if the constant source is a value that is ignorable
727-
* e.g., if it is 2 and the sink is a month assignment, then it isn't ignorable.
728+
* e.g., if it is 2 and the sink is a month assignment, then it isn't ignorable or
729+
* if the value is < 27 and is a day assignment, it is likely ignorable
728730
*
729731
* Obviously this does not handle all conditions (e.g., the month set in another block).
730732
* It is meant to capture the most common cases of false positives.
731733
*/
732-
module NonFebConstantToMonthAssignmentConfig implements DataFlow::ConfigSig {
733-
predicate isSource(DataFlow::Node source) {
734-
source.asExpr().getValue().toInt() in [1 .. 12] and
735-
source.asExpr().getValue().toInt() != 2
736-
}
734+
module CandidateConstantToDayOrMonthAssignmentConfig implements DataFlow::ConfigSig {
735+
predicate isSource(DataFlow::Node source) { source.asExpr().getValue().toInt() in [1 .. 31] }
737736

738737
predicate isSink(DataFlow::Node sink) {
739738
exists(Assignment a |
740-
a.getLValue() instanceof MonthFieldAccess and
739+
(a.getLValue() instanceof MonthFieldAccess or a.getLValue() instanceof DayFieldAccess) and
741740
a.getRValue() = sink.asExpr()
742741
)
743742
}
744743
}
745744

746745
// NOTE: only data flow here (no taint tracking) as we want the exact
747746
// constant flowing to the month assignment
748-
module NonFebConstantToMonthAssignmentFlow =
749-
DataFlow::Global<NonFebConstantToMonthAssignmentConfig>;
747+
module CandidateConstantToDayOrMonthAssignmentFlow =
748+
DataFlow::Global<CandidateConstantToDayOrMonthAssignmentConfig>;
750749

751750
import OperationToYearAssignmentFlow::PathGraph
752751

@@ -759,10 +758,73 @@ where
759758
// and the month value would indicate its set to any other month than february.
760759
// Finds if the source year node is in the same block as a source month block
761760
// and if the same for the sinks.
762-
not exists(DataFlow::Node monthValSrc, DataFlow::Node monthValSink |
763-
NonFebConstantToMonthAssignmentFlow::flow(monthValSrc, monthValSink) and
764-
monthValSrc.asExpr().getBasicBlock() = src.getNode().asExpr().getBasicBlock() and
765-
monthValSink.asExpr().getBasicBlock() = sink.getNode().asExpr().getBasicBlock()
761+
not exists(DataFlow::Node dayOrMonthValSrc, DataFlow::Node dayOrMonthValSink, Assignment a |
762+
CandidateConstantToDayOrMonthAssignmentFlow::flow(dayOrMonthValSrc, dayOrMonthValSink) and
763+
a.getRValue() = dayOrMonthValSink.asExpr() and
764+
dayOrMonthValSrc.asExpr().getBasicBlock() = src.getNode().asExpr().getBasicBlock() and
765+
dayOrMonthValSink.asExpr().getBasicBlock() = sink.getNode().asExpr().getBasicBlock() and
766+
(
767+
a.getLValue() instanceof MonthFieldAccess and
768+
dayOrMonthValSrc.asExpr().getValue().toInt() != 2
769+
or
770+
a.getLValue() instanceof DayFieldAccess and
771+
dayOrMonthValSrc.asExpr().getValue().toInt() <= 27
772+
)
766773
)
767774
select sink, src, sink,
768775
"Year field has been modified, but no appropriate check for LeapYear was found."
776+
// int limit() { result = 5 }
777+
// module Partial = TimeStructToCheckedTimeConversionFlow::FlowExplorationFwd<limit/0>;
778+
// import Partial::PartialPathGraph
779+
// from Partial::PartialPathNode src, Partial::PartialPathNode sink
780+
// where Partial::partialFlow(src, sink, _) and src.getLocation().getStartLine() = 1331
781+
// select sink, src, sink, "RequestHandle use path"
782+
// TODO: this is an older heuristic that should be reassessed.
783+
// The heuristic stipulates that if a month or day is set to a constant in the local flow up to or after
784+
// a modified year's sink, then assume the user is knowingly handling the conversion correctly.
785+
// There is no interpretation of the assignment of the month/day or consideration for what branch the assignment is on.
786+
// Merely if the assignment of a constant day/month occurs anywhere, the year modification can likely
787+
// be ignored.
788+
// not exists(FieldAccess mfa, AssignExpr ae, YearFieldAccess yfa, int val, Variable var |
789+
// mfa instanceof MonthFieldAccess or mfa instanceof DayFieldAccess
790+
// |
791+
// yfa = sink.getNode().asExpr().(YearFieldAssignment).getYearFieldAccess() and
792+
// var.getAnAccess() = yfa.getQualifier*() and
793+
// mfa.getQualifier*() = var.getAnAccess() and
794+
// mfa.isModified() and
795+
// (
796+
// mfa.getBasicBlock() = yfa.getBasicBlock().getASuccessor*() or
797+
// yfa.getBasicBlock() = mfa.getBasicBlock().getASuccessor+()
798+
// ) and
799+
// ae = mfa.getEnclosingElement() and
800+
// ae.getAnOperand().getValue().toInt() = val and
801+
// // If the constant looks like it relates to february, then there still might be an error
802+
// // so only look for any other constant.
803+
// not val in [2, 28, 29]
804+
// ) and
805+
// // This is a quick huerstic to account for similar cases as the above heuristic for constant
806+
// // months, but sometimes the month is set in a time struct inter-procedurally.
807+
// // e.g.,
808+
// // year++;
809+
// // month = 1;
810+
// // ... values passed to function that sets in a time struct
811+
// // It is tricky to catch these situations with CodeQL. An AI should be able to detect it
812+
// // however, in the meantime, a simple heuristic is to check within the same block as
813+
// // the year assignment source, if there is another variable that looks like a month
814+
// // set to a value that isn't 2, 28, or 29. Of course this is a workaround as it is
815+
// // still possible for the user to incorrectly account for leap year (e.g., by not using
816+
// // the month set in the block), but the rationale is it is more typically indicative
817+
// // of a false positive in these cases.
818+
// // TODO: can we remove this later an use an AI assessment to filter false positives?
819+
// not exists(Variable v, VariableAccess va, BasicBlock bb, AssignExpr a, int val |
820+
// v.getName().toLowerCase().matches("%month%") and
821+
// va = v.getAnAccess() and
822+
// va.getBasicBlock() = bb and
823+
// bb = src.getNode().asExpr().getBasicBlock() and
824+
// a.getBasicBlock() = bb and
825+
// a.getLValue() = va and
826+
// a.getRValue().getValue().toInt() = val and
827+
// // If the constant looks like it relates to february, then there still might be an error
828+
// // so only look for any other constant.
829+
// val != 2
830+
// )

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
| test.cpp:1204:16:1204:19 | year | test.cpp:1202:10:1202:15 | offset | test.cpp:1204:16:1204:19 | year | Year field has been modified, but no appropriate check for LeapYear was found. |
1616
| test.cpp:1243:16:1243:28 | ... + ... | test.cpp:1243:16:1243:28 | ... + ... | test.cpp:1243:16:1243:28 | ... + ... | Year field has been modified, but no appropriate check for LeapYear was found. |
1717
| test.cpp:1257:16:1257:28 | ... + ... | test.cpp:1257:16:1257:28 | ... + ... | test.cpp:1257:16:1257:28 | ... + ... | Year field has been modified, but no appropriate check for LeapYear was found. |
18+
| test.cpp:1291:14:1291:17 | year | test.cpp:1382:12:1382:17 | ... + ... | test.cpp:1291:14:1291:17 | year | Year field has been modified, but no appropriate check for LeapYear was found. |
1819
edges
1920
| test.cpp:854:7:854:31 | ... = ... | test.cpp:857:33:857:36 | year | provenance | |
2021
| test.cpp:854:14:854:31 | ... + ... | test.cpp:854:7:854:31 | ... = ... | provenance | |
@@ -32,6 +33,8 @@ edges
3233
| test.cpp:1325:3:1325:20 | ... = ... | test.cpp:1327:12:1327:18 | yeartmp | provenance | |
3334
| test.cpp:1325:13:1325:20 | ... + ... | test.cpp:1325:3:1325:20 | ... = ... | provenance | |
3435
| test.cpp:1327:12:1327:18 | yeartmp | test.cpp:1288:20:1288:23 | year | provenance | |
36+
| test.cpp:1375:12:1375:17 | ... + ... | test.cpp:1288:20:1288:23 | year | provenance | |
37+
| test.cpp:1382:12:1382:17 | ... + ... | test.cpp:1288:20:1288:23 | year | provenance | |
3538
nodes
3639
| test.cpp:422:14:422:14 | 2 | semmle.label | 2 |
3740
| test.cpp:440:2:440:11 | ... ++ | semmle.label | ... ++ |
@@ -90,6 +93,9 @@ nodes
9093
| test.cpp:1327:12:1327:18 | yeartmp | semmle.label | yeartmp |
9194
| test.cpp:1340:15:1340:27 | ... + ... | semmle.label | ... + ... |
9295
| test.cpp:1353:13:1353:25 | ... + ... | semmle.label | ... + ... |
96+
| test.cpp:1370:15:1370:22 | ... + ... | semmle.label | ... + ... |
97+
| test.cpp:1375:12:1375:17 | ... + ... | semmle.label | ... + ... |
98+
| test.cpp:1382:12:1382:17 | ... + ... | semmle.label | ... + ... |
9399
subpaths
94100
testFailures
95101
| test.cpp:854:43:854:53 | // $ Source | Missing result: Source |

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

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,7 +1288,7 @@ void indirect_time_conversion_check(WORD year, WORD offset){
12881288
void set_time(WORD year, WORD month, WORD day){
12891289
SYSTEMTIME tmp;
12901290

1291-
tmp.wYear = year;
1291+
tmp.wYear = year; //$ Alert[cpp/microsoft/public/leap-year/unchecked-after-arithmetic-year-modification]
12921292
tmp.wMonth = month;
12931293
tmp.wDay = day;
12941294
}
@@ -1303,7 +1303,7 @@ void constant_month_on_year_modification1(WORD year, WORD offset, WORD month){
13031303

13041304
if(month++ > 12){
13051305

1306-
set_time(year+1, 1, 1);// OK since the year is incremented with a known non-leap year month change
1306+
set_time(year+1, 1, 31);// OK since the year is incremented with a known non-leap year month change
13071307
}
13081308
}
13091309

@@ -1324,7 +1324,7 @@ void constant_month_on_year_modification2(WORD year, WORD offset, WORD month){
13241324
WORD monthtmp;
13251325
yeartmp = year + 1;
13261326
monthtmp = 1;
1327-
set_time(yeartmp, monthtmp, 1);// OK since the year is incremented with a known non-leap year month change
1327+
set_time(yeartmp, monthtmp, 31);// OK since the year is incremented with a known non-leap year month change
13281328
}
13291329
}
13301330

@@ -1362,3 +1362,23 @@ void intermediate_time_struct(WORD year, WORD offset){
13621362

13631363
}
13641364

1365+
void constant_day_on_year_modification1(WORD year, WORD offset, WORD month){
1366+
SYSTEMTIME tmp;
1367+
1368+
if(month++ > 12){
1369+
tmp.wDay = 1;
1370+
tmp.wYear = year + 1;// OK since the year is incremented with a known non-leap year day
1371+
}
1372+
1373+
if(month++ > 12){
1374+
1375+
set_time(year+1, month, 1);// OK since the year is incremented with a known non-leap year day
1376+
}
1377+
1378+
if(month++ > 12){
1379+
1380+
// BAD, year incremented, month unknown in block, and date is set to 31
1381+
// which is dangerous.
1382+
set_time(year+1, month, 31);// $ Source
1383+
}
1384+
}

0 commit comments

Comments
 (0)