Skip to content

Commit 65d9a9d

Browse files
committed
Now using asDefinition to detect assignment to a year field. Misc cleanup. Updated expected file.
1 parent 8f1d8d4 commit 65d9a9d

File tree

3 files changed

+159
-222
lines changed

3 files changed

+159
-222
lines changed

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

Lines changed: 40 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,6 @@ class IgnorableFunction extends Function {
6767
this.getName().toLowerCase().matches("%persian%")
6868
or
6969
this.getFile().getBaseName().toLowerCase().matches("%persian%")
70-
// or
71-
// // Functions with "convert" in the name are often conversion functions
72-
// // This is a very broad heuristic to handle a few observed false positives
73-
// // involving conversions with Hijri years.
74-
// this.getName().toLowerCase().matches("%convert%")
7570
}
7671
}
7772

@@ -265,9 +260,9 @@ predicate isOperationSourceCandidate(Expr e) {
265260
or
266261
e instanceof CrementOperation
267262
or
268-
exists(AssignSubExpr a | a.getRValue() = e)
263+
e instanceof AssignSubExpr
269264
or
270-
exists(AssignAddExpr a | a.getRValue() = e)
265+
e instanceof AssignAddExpr
271266
)
272267
}
273268

@@ -313,64 +308,26 @@ class OperationSource extends Expr {
313308
}
314309

315310
class YearFieldAssignmentNode extends DataFlow::Node {
311+
YearFieldAccess access;
312+
316313
YearFieldAssignmentNode() {
317314
not this.getEnclosingCallable().getUnderlyingCallable() instanceof IgnorableFunction and
318315
(
319-
this.asExpr() instanceof YearFieldAssignment or
320-
this.asDefiningArgument() instanceof YearFieldOutArgAssignment
321-
)
322-
}
323-
}
324-
325-
/**
326-
* An assignment to a year field, which will be a sink
327-
*/
328-
abstract class YearFieldAssignment extends Expr {
329-
abstract YearFieldAccess getYearFieldAccess();
330-
}
331-
332-
class YearFieldOutArgAssignment extends YearFieldAssignment {
333-
YearFieldAccess access;
334-
335-
YearFieldOutArgAssignment() {
336-
exists(Call c | c.getAnArgument() = access and this = access)
337-
or
338-
exists(Call c, AddressOfExpr aoe |
339-
c.getAnArgument() = aoe and
340-
aoe.getOperand() = access and
341-
this = aoe
342-
)
343-
}
344-
345-
override YearFieldAccess getYearFieldAccess() { result = access }
346-
}
347-
348-
/**
349-
* An assignment to x ie `x = a`.
350-
*/
351-
class YearFieldAssignmentAccess extends YearFieldAssignment {
352-
YearFieldAccess access;
353-
354-
YearFieldAssignmentAccess() {
355-
// TODO: why can't I make this just the L value? Doesn't seem to work
356-
exists(Assignment a |
357-
a.getLValue() = access and
358-
a.getRValue() = this
316+
this.asDefinition().(Assignment).getLValue() = access
317+
or
318+
this.asDefinition().(CrementOperation).getOperand() = access
319+
or
320+
exists(Call c | c.getAnArgument() = access and this.asDefiningArgument() = access)
321+
or
322+
exists(Call c, AddressOfExpr aoe |
323+
c.getAnArgument() = aoe and
324+
aoe.getOperand() = access and
325+
this.asDefiningArgument() = aoe
326+
)
359327
)
360328
}
361329

362-
override YearFieldAccess getYearFieldAccess() { result = access }
363-
}
364-
365-
/**
366-
* A year field assignment, by a unary operator ie `x++`.
367-
*/
368-
class YearFieldAssignmentUnary extends YearFieldAssignment instanceof CrementOperation {
369-
YearFieldAccess access;
370-
371-
YearFieldAssignmentUnary() { this.getOperand() = access }
372-
373-
override YearFieldAccess getYearFieldAccess() { result = access }
330+
YearFieldAccess getYearFieldAccess() { result = access }
374331
}
375332

376333
/**
@@ -423,7 +380,7 @@ predicate isLeapYearCheckSink(DataFlow::Node sink) {
423380
/**
424381
* A flow configuration from a Year field access to some Leap year check or guard
425382
*/
426-
module YearFieldAccessToLeapYearCheckConfig implements DataFlow::ConfigSig {
383+
module YearAssignmentToLeapYearCheckConfig implements DataFlow::ConfigSig {
427384
predicate isSource(DataFlow::Node source) { source instanceof YearFieldAssignmentNode }
428385

429386
predicate isSink(DataFlow::Node sink) { isLeapYearCheckSink(sink) }
@@ -441,6 +398,13 @@ module YearFieldAccessToLeapYearCheckConfig implements DataFlow::ConfigSig {
441398
or
442399
// flow from a year access qualifier to a year field
443400
exists(YearFieldAccess yfa | node2.asExpr() = yfa and node1.asExpr() = yfa.getQualifier())
401+
or
402+
// in cases of x.year = x and the x is checked, but the year x.year isn't directly
403+
// flow from a year assignment node to an RHS if it is an assignment
404+
exists(YearFieldAssignmentNode yfan |
405+
node1 = yfan and
406+
node2.asExpr() = yfan.asDefinition().(Assignment).getRValue()
407+
)
444408
}
445409

446410
/**
@@ -450,36 +414,24 @@ module YearFieldAccessToLeapYearCheckConfig implements DataFlow::ConfigSig {
450414
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
451415
}
452416

453-
module YearFieldAccessToLeapYearCheckFlow =
454-
TaintTracking::Global<YearFieldAccessToLeapYearCheckConfig>;
417+
module YearAssignmentToLeapYearCheckFlow =
418+
TaintTracking::Global<YearAssignmentToLeapYearCheckConfig>;
455419

456420
/** Does there exist a flow from the given YearFieldAccess to a Leap Year check or guard? */
457-
predicate isYearModifiedWithCheck(YearFieldAccess fa) {
458-
exists(YearFieldAccessToLeapYearCheckFlow::PathNode src |
421+
predicate isYearModifiedWithCheck(YearFieldAssignmentNode n) {
422+
exists(YearAssignmentToLeapYearCheckFlow::PathNode src |
459423
src.isSource() and
460-
src.getNode().asExpr().(YearFieldAssignment).getYearFieldAccess() = fa
424+
src.getNode() = n
461425
)
462426
or
463427
// If the time flows to a time conversion whose value/result is checked,
464428
// assume the leap year is being handled.
465-
exists(TimeStructToCheckedTimeConversionFlow::PathNode timeQualSrc |
429+
exists(YearAssignmentToCheckedTimeConversionFlow::PathNode timeQualSrc |
466430
timeQualSrc.isSource() and
467-
timeQualSrc.getNode().asExpr() = fa.getQualifier*()
431+
timeQualSrc.getNode() = n
468432
)
469-
// or
470-
// isUsedInFeb29Check(fa)
471433
}
472434

473-
// /**
474-
// * If there is a flow from a date struct year field access/assignment to a Feb 29 check
475-
// */
476-
// predicate isUsedInFeb29Check(YearFieldAccess fa) {
477-
// exists(DateFebruary29Check check |
478-
// DataFlow::localExprFlow(fa.getQualifier(), check.getDateQualifier())
479-
// or
480-
// DataFlow::localExprFlow(check.getDateQualifier(), fa.getQualifier())
481-
// )
482-
// }
483435
/**
484436
* An expression which checks the value of a Month field `a->month == 1`.
485437
*/
@@ -516,11 +468,11 @@ predicate isControlledByMonthEqualityCheckNonFebruary(Expr e) {
516468
* assume some sort of error handling is occurring that could be used
517469
* to detect bad dates due to leap year.
518470
*/
519-
module TimeStructToCheckedTimeConversionConfig implements DataFlow::StateConfigSig {
471+
module YearAssignmentToCheckedTimeConversionConfig implements DataFlow::StateConfigSig {
520472
class FlowState = boolean;
521473

522474
predicate isSource(DataFlow::Node source, FlowState state) {
523-
exists(YearFieldAccess yfa | source.asExpr() = yfa.getQualifier()) and
475+
source instanceof YearFieldAssignmentNode and
524476
state = false
525477
}
526478

@@ -553,6 +505,8 @@ module TimeStructToCheckedTimeConversionConfig implements DataFlow::StateConfigS
553505
// flow from a YearFieldAccess to the qualifier
554506
node2.asExpr() = node1.asExpr().(YearFieldAccess).getQualifier*()
555507
or
508+
node1.(YearFieldAssignmentNode).getYearFieldAccess().getQualifier() = node2.asExpr()
509+
or
556510
// Pass through any intermediate struct
557511
exists(Assignment a, DataFlow::PostUpdateNode pun |
558512
a.getLValue().(YearFieldAccess).getQualifier*() = pun.getPreUpdateNode().asExpr() and
@@ -567,8 +521,8 @@ module TimeStructToCheckedTimeConversionConfig implements DataFlow::StateConfigS
567521
DataFlow::FlowFeature getAFeature() { result instanceof DataFlow::FeatureHasSourceCallContext }
568522
}
569523

570-
module TimeStructToCheckedTimeConversionFlow =
571-
DataFlow::GlobalWithState<TimeStructToCheckedTimeConversionConfig>;
524+
module YearAssignmentToCheckedTimeConversionFlow =
525+
DataFlow::GlobalWithState<YearAssignmentToCheckedTimeConversionConfig>;
572526

573527
/**
574528
* Finds flow from a parameter of a function to a leap year check.
@@ -752,7 +706,7 @@ import OperationToYearAssignmentFlow::PathGraph
752706
from OperationToYearAssignmentFlow::PathNode src, OperationToYearAssignmentFlow::PathNode sink
753707
where
754708
OperationToYearAssignmentFlow::flowPath(src, sink) and
755-
not isYearModifiedWithCheck(sink.getNode().asExpr().(YearFieldAssignment).getYearFieldAccess()) and
709+
not isYearModifiedWithCheck(sink.getNode()) and
756710
not isControlledByMonthEqualityCheckNonFebruary(sink.getNode().asExpr()) and
757711
// Check if a month is set in the same block as the year operation source
758712
// and the month value would indicate its set to any other month than february.
@@ -761,8 +715,8 @@ where
761715
not exists(DataFlow::Node dayOrMonthValSrc, DataFlow::Node dayOrMonthValSink, Assignment a |
762716
CandidateConstantToDayOrMonthAssignmentFlow::flow(dayOrMonthValSrc, dayOrMonthValSink) and
763717
a.getRValue() = dayOrMonthValSink.asExpr() and
764-
dayOrMonthValSrc.asExpr().getBasicBlock() = src.getNode().asExpr().getBasicBlock() and
765-
dayOrMonthValSink.asExpr().getBasicBlock() = sink.getNode().asExpr().getBasicBlock() and
718+
dayOrMonthValSrc.getBasicBlock() = src.getNode().getBasicBlock() and
719+
dayOrMonthValSink.getBasicBlock() = sink.getNode().getBasicBlock() and
766720
(
767721
a.getLValue() instanceof MonthFieldAccess and
768722
dayOrMonthValSrc.asExpr().getValue().toInt() != 2
@@ -773,58 +727,3 @@ where
773727
)
774728
select sink, src, sink,
775729
"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-
// )

0 commit comments

Comments
 (0)