@@ -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
751750import 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 )
767774select 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+ // )
0 commit comments