-
Notifications
You must be signed in to change notification settings - Fork 1.9k
C++: Refactor of UncheckedLeapYearAfterModification #21292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
C++: Refactor of UncheckedLeapYearAfterModification #21292
Conversation
…cks (UncheckedLeapYearAfterYearModification). Switch to using 'postprocess' for unit tests.
…ion. Includes new logic for detecting leap year checks, new forms of leap year checks detected, and various heuristics to remove false postives. Move TimeConversionFunction into LeapYear.qll and refactored to separate conversion functions that are expected to be checked for failure from those that auto correct leap year dates if feb 29 is provided on a non-leap year. Increas the set of known TimeConversionFunctions.
…e negative remains.
…auto correct for leap year should be considered.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Refactors the UncheckedLeapYearAfterYearModification CodeQL query to significantly reduce false positives by introducing more precise flow/guard modeling and expanded heuristics around conversions and “ignorable” operations.
Changes:
- Reworked
UncheckedLeapYearAfterYearModification.qlinto a path-problem with new taint/dataflow-based modeling and multiple false-positive suppression heuristics. - Centralized/expanded time conversion API modeling in
LeapYear.qlland updated related query/tests/expected outputs accordingly. - Extended date/time type support (e.g.,
TIME_FIELDS) and added release change notes.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp | Adds many new positive/negative/edge test scenarios for the refactored query. |
| cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedReturnValueForTimeFunctions.expected | Updates expected results to match new line numbers/coverage. |
| cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedLeapYearAfterYearModification.qlref | Switches to inline-expectations postprocessing. |
| cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/UncheckedLeapYearAfterYearModification.expected | Updates expected results to reflect path-problem output and new test suite. |
| cpp/ql/src/Likely Bugs/Leap Year/UncheckedReturnValueForTimeFunctions.ql | Refines conversion-return checking to exclude auto-leap-year-correcting APIs. |
| cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql | Full refactor: new flow configurations, guard recognition, and suppression heuristics. |
| cpp/ql/src/Likely Bugs/Leap Year/LeapYear.qll | Moves/expands TimeConversionFunction modeling and adds auto-correcting classification. |
| cpp/ql/lib/semmle/code/cpp/commons/DateTime.qll | Expands recognized unpacked time/date types and adds field-access classes for TIME_FIELDS. |
| cpp/ql/lib/change-notes/2026-02-06-UncheckedLeapYearAfterModification_Refactor | Adds changelog entry documenting the refactor and alert reduction. |
cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp
Show resolved
Hide resolved
cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp
Outdated
Show resolved
Hide resolved
cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp
Outdated
Show resolved
Hide resolved
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Outdated
Show resolved
Hide resolved
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Outdated
Show resolved
Hide resolved
cpp/ql/test/query-tests/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification/test.cpp
Outdated
Show resolved
Hide resolved
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
cpp/ql/src/Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql
Fixed
Show fixed
Hide fixed
geoffw0
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial review - lots of small comments, though little in particular I have strong feelings about. I'm happy to see this query get an overhaul, I'm very happy to see lots of test cases, though there is more query code than I'd like from a readability perspective. It would be nice to have some kind of diagram of how all the flows to fit together to aid understanding.
If you make whichever improvements you feel are most valuable, then I expect to approve this. I do also want to briefly check:
- performance
- real world (MRVA) result changes
- CI
| // BUG - UncheckedLeapYearAfterYearModification | ||
| st.wYear++; | ||
| // Safe, checked interprocedurally through Correct_filetime_conversion_check | ||
| st.wYear++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does Correct_filetime_conversion_check correct st?
| timeinfo.tm_year++; | ||
|
|
||
| // Usage of potentially invalid date | ||
| // mkgmtime result checked in nested call here, assume leap year conversion is potentially handled |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question here, how does CorrectUsageOf_mkgmtime correct the problem?
| pst->wMonth = 1; | ||
| pst->wYear++; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function looks bad to me, though I suppose the issue is incrementing the month without a days-in-the-month check - not incrementing the year without a leap-year check. I guess that's the responsibility of a different query (that I think doesn't exist yet).
| TaintTracking::Global<PossibleYearArithmeticOperationCheckConfig>; | ||
|
|
||
| /** | ||
| * This list of APIs should check for the return value to detect problems during the conversion. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * This list of APIs should check for the return value to detect problems during the conversion. | |
| * Time conversion functions with a return value that should be checked to detect problems during the conversion. |
| */ | ||
| class IgnorableFunction extends Function { | ||
| IgnorableFunction() { | ||
| this instanceof TimeConversionFunction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd like a comment explaining the presence of TimeConversionFunction here. I think the idea is that these functions will return an error which, assuming its properly handled, allows the leap year problem to be detected and corrected (or else cause the whole operation to fail without doing something unexpected with the invalid date).
| // in cases of x.year = x and the x is checked, but the year x.year isn't directly | ||
| // flow from a year assignment node to an RHS if it is an assignment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't follow this comment, can you rephrase?
| * auto convert to a sanity check guard of the result for error conditions. | ||
| */ | ||
| module YearAssignmentToCheckedTimeConversionConfig implements DataFlow::StateConfigSig { | ||
| class FlowState = boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would help readability if we briefly explain what FlowState is tracking here. My understanding is its used to ensure flow goes through the steps in order from source -> TimeConversionFunction -> check condition, unless we reach a TimeConversionFunction with .isAutoLeapYearCorrecting() in which case that is a simple unconditional sink.
| or | ||
| // flow from a year access qualifier to a year field | ||
| exists(YearFieldAccess yfa | node2.asExpr() = yfa and node1.asExpr() = yfa.getQualifier()) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of these cases look very similar to cases in YearAssignmentToLeapYearCheckConfig. We could perhaps take the common code out and name it.
| * ... values eventually used in the same time struct | ||
| * If this is even more challenging if the struct the values end up in are not | ||
| * local (set inter-procedurally). | ||
| * This flow flows constants 1-31 to a month or day assignment. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * This flow flows constants 1-31 to a month or day assignment. | |
| * This configuration looks for constants 1-31 flowing to a month or day assignment. |
| --- | ||
| category: minorAnalysis | ||
| --- | ||
| * Refactor of UncheckedLeapYearAfterYearModification.ql to address large numbers of false positives. Reduced alerts from 40k to 2k. No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| * Refactor of UncheckedLeapYearAfterYearModification.ql to address large numbers of false positives. Reduced alerts from 40k to 2k. | |
| * Refactored the "Year field changed using an arithmetic operation without checking for leap year" query (`cpp/leap-year/unchecked-after-arithmetic-year-modification`) to address large numbers of false positive results. |
The number of alerts is nice, but will be meaningless to a general audience.
This PR addresses excessive false positive alerting from
Likely Bugs/Leap Year/UncheckedLeapYearAfterYearModification.ql. In separate in-depth auditing, the number of alerts drops from 40,000 down to 2,000 with these changes, with a much higher rate of true positives, though still too high to be considered more than medium precision (~25% false positive rate remaining).This PR is a complete refactor of the original query to address the false positives observed on production code. Some of the lessons learned here could be extrapolated into the LeapYear.qll library, but leaving changes like this for future PRs.