Skip to content

Commit 936ecfc

Browse files
committed
All remaining leap year ql and qhelp files.
1 parent 7eee4f2 commit 936ecfc

10 files changed

+452
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @name Leap Year Invalid Check (AntiPattern 5)
3+
* @description An expression is used to check a year is presumably a leap year, but the conditions used are insufficient.
4+
* @kind problem
5+
* @problem.severity error
6+
* @id cpp/leap-year/invalid-leap-year-check
7+
* @precision medium
8+
* @tags leap-year
9+
* correctness
10+
* security
11+
*/
12+
13+
import cpp
14+
import LeapYear
15+
16+
from Mod4CheckedExpr exprMod4
17+
where not exists(ExprCheckLeapYear lyCheck | lyCheck.getAChild*() = exprMod4)
18+
select exprMod4, "Possible Insufficient Leap Year check (AntiPattern 5)"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<fragment>
6+
<p>The leap year rule for the Gregorian calendar, which has become the internationally accepted civil calendar, is: every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400.</p>
7+
<p>A leap year bug occurs when software (in any language) is written without consideration of leap year logic, or with flawed logic to calculate leap years; which typically results in incorrect results.</p>
8+
<p>The impact of these bugs may range from almost unnoticeable bugs such as an incorrect date, to severe bugs that affect reliability, availability or even the security of the affected system.</p>
9+
</fragment>
10+
</qhelp>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<include src="LeapYear.inc.qhelp" />
7+
<p>This anti-pattern occurs when a developer uses conditional logic to execute a different path of code for a leap year than for a common year, without fully testing both code paths.</p>
8+
<p>Though using a framework or library's leap year function is better than manually calculating the leap year (as described in anti-pattern 5), it can still be a source of errors if the result is used to execute a different code path. The bug is especially easy to be masked if the year is derived from the current time of the system clock. See Prevention Measures for techniques to avoid this bug.</p>
9+
</overview>
10+
<recommendation>
11+
<ul>
12+
<li>Avoid using conditional logic that creates a separate branch in your code for leap year.</li>
13+
<li>Ensure your code is testable, and test how it will behave when presented with leap year dates of February 29th and December 31st as inputs.</li>
14+
</ul>
15+
</recommendation>
16+
<example>
17+
<p>Note in the following examples, that year, month, and day might instead be .wYear, .wMonth, and .wDay fields of a SYSTEMTIME structure, or might be .tm_year, .tm_mon, and .tm_mday fields of a struct tm.</p>
18+
<sample src="LeapYearConditionalLogicBad.c" />
19+
</example>
20+
21+
<references>
22+
<li>NASA / Goddard Space Flight Center - <a href="https://eclipse.gsfc.nasa.gov/SEhelp/calendars.html">Calendars</a></li>
23+
<li>Wikipedia - <a href="https://en.wikipedia.org/wiki/Leap_year_bug"> Leap year bug</a> </li>
24+
<li>Microsoft Azure blog - <a href="https://azure.microsoft.com/en-us/blog/is-your-code-ready-for-the-leap-year/"> Is your code ready for the leap year?</a> </li>
25+
</references>
26+
</qhelp>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* @name Leap Year Conditional Logic (AntiPattern 7)
3+
* @description Conditional logic is present for leap years and common years, potentially leading to untested code pathways.
4+
* @kind problem
5+
* @problem.severity error
6+
* @id cpp/leap-year/conditional-logic-branches
7+
* @precision medium
8+
* @tags leap-year
9+
* correctness
10+
* security
11+
*/
12+
13+
import cpp
14+
import LeapYear
15+
import semmle.code.cpp.dataflow.new.DataFlow
16+
17+
class IfStmtLeapYearCheck extends IfStmt {
18+
IfStmtLeapYearCheck() {
19+
this.hasElse() and
20+
exists(ExprCheckLeapYear lyCheck, DataFlow::Node source, DataFlow::Node sink |
21+
source.asExpr() = lyCheck and
22+
sink.asExpr() = this.getCondition() and
23+
DataFlow::localFlow(source, sink)
24+
)
25+
}
26+
}
27+
28+
from IfStmtLeapYearCheck lyCheckIf
29+
select lyCheckIf, "Leap Year conditional statement may have untested code paths"
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<include src="LeapYear.inc.qhelp" />
7+
8+
<p>When performing arithmetic operations on a variable that represents a year, it is important to consider that the resulting value may not be a valid date.</p>
9+
<p>The typical example is doing simple year arithmetic (i.e. <code>date.year++</code>) without considering if the resulting value will be a valid date or not.</p>
10+
11+
</overview>
12+
<recommendation>
13+
<p>When modifying a year field on a date structure, verify if the resulting year is a leap year.</p>
14+
15+
</recommendation>
16+
<example>
17+
<p>In this example, we are adding 1 year to the current date. This may work most of the time, but on any given February 29th, the resulting value will be invalid.</p>
18+
<sample src="examples/UncheckedLeapYearAfterYearModificationBad.c" />
19+
20+
<p>To fix this bug, check the result for leap year.</p>
21+
<sample src="examples/UncheckedLeapYearAfterYearModificationGood.c" />
22+
</example>
23+
24+
<references>
25+
<li>NASA / Goddard Space Flight Center - <a href="https://eclipse.gsfc.nasa.gov/SEhelp/calendars.html">Calendars</a></li>
26+
<li>Wikipedia - <a href="https://en.wikipedia.org/wiki/Leap_year_bug"> Leap year bug</a> </li>
27+
<li>Microsoft Azure blog - <a href="https://azure.microsoft.com/en-us/blog/is-your-code-ready-for-the-leap-year/"> Is your code ready for the leap year?</a> </li>
28+
</references>
29+
</qhelp>
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/**
2+
* @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.
4+
* @kind problem
5+
* @problem.severity error
6+
* @id cpp/leap-year/unchecked-after-arithmetic-year-modification
7+
* @precision medium
8+
* @tags leap-year
9+
* correctness
10+
* security
11+
*/
12+
13+
import cpp
14+
import LeapYear
15+
16+
/**
17+
* Holds if there is no known leap-year verification for the given `YearWriteOp`.
18+
* Binds the `var` argument to the qualifier of the `ywo` argument.
19+
*/
20+
bindingset[ywo]
21+
predicate isYearModifedWithoutExplicitLeapYearCheck(Variable var, YearWriteOp ywo) {
22+
exists(VariableAccess va, YearFieldAccess yfa |
23+
yfa = ywo.getYearAccess() and
24+
yfa.getQualifier() = va and
25+
var.getAnAccess() = va and
26+
// Avoid false positives
27+
not (
28+
// If there is a local check for leap year after the modification
29+
exists(LeapYearFieldAccess yfacheck |
30+
yfacheck.getQualifier() = var.getAnAccess() and
31+
yfacheck.isUsedInCorrectLeapYearCheck() and
32+
yfacheck.getBasicBlock() = yfa.getBasicBlock().getASuccessor*()
33+
)
34+
or
35+
// If there is a data flow from the variable that was modified to a function that seems to check for leap year
36+
exists(VariableAccess source, ChecksForLeapYearFunctionCall fc |
37+
source = var.getAnAccess() and
38+
LeapYearCheckFlow::flow(DataFlow::exprNode(source), DataFlow::exprNode(fc.getAnArgument()))
39+
)
40+
or
41+
// If there is a data flow from the field that was modified to a function that seems to check for leap year
42+
exists(VariableAccess vacheck, YearFieldAccess yfacheck, ChecksForLeapYearFunctionCall fc |
43+
vacheck = var.getAnAccess() and
44+
yfacheck.getQualifier() = vacheck and
45+
LeapYearCheckFlow::flow(DataFlow::exprNode(yfacheck), DataFlow::exprNode(fc.getAnArgument()))
46+
)
47+
or
48+
// If there is a successor or predecessor that sets the month or day to a fixed value
49+
exists(FieldAccess mfa, AssignExpr ae, int val |
50+
mfa instanceof MonthFieldAccess or mfa instanceof DayFieldAccess
51+
|
52+
mfa.getQualifier() = var.getAnAccess() and
53+
mfa.isModified() and
54+
(
55+
mfa.getBasicBlock() = yfa.getBasicBlock().getASuccessor*() or
56+
yfa.getBasicBlock() = mfa.getBasicBlock().getASuccessor+()
57+
) and
58+
ae = mfa.getEnclosingElement() and
59+
ae.getAnOperand().getValue().toInt() = val
60+
)
61+
)
62+
)
63+
}
64+
65+
/**
66+
* The set of all write operations to the Year field of a date struct.
67+
*/
68+
abstract class YearWriteOp extends Operation {
69+
/** Extracts the access to the Year field */
70+
abstract YearFieldAccess getYearAccess();
71+
72+
/** Get the expression which represents the new value. */
73+
abstract Expr getMutationExpr();
74+
}
75+
76+
/**
77+
* A unary operation (Crement) performed on a Year field.
78+
*/
79+
class YearWriteOpUnary extends YearWriteOp, UnaryOperation {
80+
YearWriteOpUnary() { this.getOperand() instanceof YearFieldAccess }
81+
82+
override YearFieldAccess getYearAccess() { result = this.getOperand() }
83+
84+
override Expr getMutationExpr() { result = this }
85+
}
86+
87+
/**
88+
* An assignment operation or mutation on the Year field of a date object.
89+
*/
90+
class YearWriteOpAssignment extends YearWriteOp, Assignment {
91+
YearWriteOpAssignment() { this.getLValue() instanceof YearFieldAccess }
92+
93+
override YearFieldAccess getYearAccess() { result = this.getLValue() }
94+
95+
override Expr getMutationExpr() {
96+
// Note: may need to use DF analysis to pull out the original value,
97+
// if there is excessive false positives.
98+
if this.getOperator() = "="
99+
then
100+
exists(DataFlow::Node source, DataFlow::Node sink |
101+
sink.asExpr() = this.getRValue() and
102+
OperationToYearAssignmentFlow::flow(source, sink) and
103+
result = source.asExpr()
104+
)
105+
else result = this
106+
}
107+
}
108+
109+
/**
110+
* A DataFlow configuration for identifying flows from some non trivial access or literal
111+
* to the Year field of a date object.
112+
*/
113+
module OperationToYearAssignmentConfig implements DataFlow::ConfigSig {
114+
predicate isSource(DataFlow::Node n) {
115+
not n.asExpr() instanceof Access and
116+
not n.asExpr() instanceof Literal
117+
}
118+
119+
predicate isSink(DataFlow::Node n) {
120+
exists(Assignment a |
121+
a.getLValue() instanceof YearFieldAccess and
122+
a.getRValue() = n.asExpr()
123+
)
124+
}
125+
}
126+
127+
module OperationToYearAssignmentFlow = DataFlow::Global<OperationToYearAssignmentConfig>;
128+
129+
from Variable var, YearWriteOp ywo, Expr mutationExpr
130+
where
131+
mutationExpr = ywo.getMutationExpr() and
132+
isYearModifedWithoutExplicitLeapYearCheck(var, ywo) and
133+
not isNormalizationOperation(mutationExpr) and
134+
not ywo instanceof AddressOfExpr and
135+
not exists(Call c, TimeConversionFunction f | f.getACallToThisFunction() = c |
136+
c.getAnArgument().getAChild*() = var.getAnAccess() and
137+
ywo.getASuccessor*() = c
138+
)
139+
select ywo,
140+
"$@: Field $@ on variable $@ has been modified, but no appropriate check for LeapYear was found.",
141+
ywo.getEnclosingFunction(), ywo.getEnclosingFunction().toString(),
142+
ywo.getYearAccess().getTarget(), ywo.getYearAccess().getTarget().toString(), var, var.toString()
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<include src="LeapYear.inc.qhelp" />
7+
8+
<p>When using a function that transforms a date structure, and the year on the input argument for the API has been manipulated, it is important to check for the return value of the function to make sure it succeeded.</p>
9+
<p>Otherwise, the function may have failed, and the output parameter may contain invalid data that can cause any number of problems on the affected system.</p>
10+
<p>The following is a list of the functions that this query covers:</p>
11+
<ul>
12+
<li><code>FileTimeToSystemTime</code></li>
13+
<li><code>SystemTimeToFileTime</code></li>
14+
<li><code>SystemTimeToTzSpecificLocalTime</code></li>
15+
<li><code>SystemTimeToTzSpecificLocalTimeEx</code></li>
16+
<li><code>TzSpecificLocalTimeToSystemTime</code></li>
17+
<li><code>TzSpecificLocalTimeToSystemTimeEx</code></li>
18+
<li><code>RtlLocalTimeToSystemTime</code></li>
19+
<li><code>RtlTimeToSecondsSince1970</code></li>
20+
<li><code>_mkgmtime</code></li>
21+
</ul>
22+
23+
</overview>
24+
<recommendation>
25+
<p>When calling an API that transforms a date variable that was manipulated, always check for the return value to verify that the API call succeeded.</p>
26+
27+
</recommendation>
28+
<example>
29+
<p>In this example, we are adding 1 year to the current date. This may work most of the time, but on any given February 29th, the resulting value will be invalid.</p>
30+
<sample src="examples/UncheckedLeapYearAfterYearModificationBad.c" />
31+
32+
<p>To fix this bug, you must verify the return value for <code>SystemTimeToFileTime</code> and handle any potential error accordingly.</p>
33+
<sample src="examples/UncheckedLeapYearAfterYearModificationGood.c" />
34+
</example>
35+
36+
<references>
37+
<li>NASA / Goddard Space Flight Center - <a href="https://eclipse.gsfc.nasa.gov/SEhelp/calendars.html">Calendars</a></li>
38+
<li>Wikipedia - <a href="https://en.wikipedia.org/wiki/Leap_year_bug"> Leap year bug</a> </li>
39+
<li>Microsoft Azure blog - <a href="https://azure.microsoft.com/en-us/blog/is-your-code-ready-for-the-leap-year/"> Is your code ready for the leap year?</a> </li>
40+
</references>
41+
</qhelp>
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* @name Unchecked return value for time conversion function (AntiPattern 6)
3+
* @description When the return value of a fallible time conversion function is
4+
* not checked for failure, its output parameters may contain
5+
* invalid dates.
6+
* @kind problem
7+
* @problem.severity error
8+
* @id cpp/leap-year/unchecked-return-value-for-time-conversion-function
9+
* @precision medium
10+
* @tags leap-year
11+
* correctness
12+
* security
13+
*/
14+
15+
import cpp
16+
import LeapYear
17+
18+
from FunctionCall fcall, TimeConversionFunction trf, Variable var
19+
where
20+
fcall = trf.getACallToThisFunction() and
21+
fcall instanceof ExprInVoidContext and
22+
var.getUnderlyingType() instanceof UnpackedTimeType and
23+
(
24+
exists(AddressOfExpr aoe |
25+
aoe = fcall.getAnArgument() and
26+
aoe.getAddressable() = var
27+
)
28+
or
29+
exists(VariableAccess va |
30+
fcall.getAnArgument() = va and
31+
var.getAnAccess() = va
32+
)
33+
) and
34+
exists(DateStructModifiedFieldAccess dsmfa, VariableAccess modifiedVarAccess |
35+
modifiedVarAccess = var.getAnAccess() and
36+
modifiedVarAccess = dsmfa.getQualifier() and
37+
modifiedVarAccess = fcall.getAPredecessor*()
38+
) and
39+
// Remove false positives
40+
not (
41+
// Remove any instance where the predecessor is a SafeTimeGatheringFunction and no change to the data happened in between
42+
exists(FunctionCall pred |
43+
pred = fcall.getAPredecessor*() and
44+
exists(SafeTimeGatheringFunction stgf | pred = stgf.getACallToThisFunction()) and
45+
not exists(DateStructModifiedFieldAccess dsmfa, VariableAccess modifiedVarAccess |
46+
modifiedVarAccess = var.getAnAccess() and
47+
modifiedVarAccess = dsmfa.getQualifier() and
48+
modifiedVarAccess = fcall.getAPredecessor*() and
49+
modifiedVarAccess = pred.getASuccessor*()
50+
)
51+
)
52+
or
53+
// Remove any instance where the year is changed, but the month is set to 1 (year wrapping)
54+
exists(MonthFieldAccess mfa, AssignExpr ae |
55+
mfa.getQualifier() = var.getAnAccess() and
56+
mfa.isModified() and
57+
mfa = fcall.getAPredecessor*() and
58+
ae = mfa.getEnclosingElement() and
59+
ae.getAnOperand().getValue().toInt() = 1
60+
)
61+
)
62+
select fcall,
63+
"$@: Return value of $@ function should be verified to check for any error because variable $@ is not guaranteed to be safe.",
64+
fcall.getEnclosingFunction(), fcall.getEnclosingFunction().toString(), trf,
65+
trf.getQualifiedName().toString(), var, var.getName()
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<include src="LeapYear.inc.qhelp" />
7+
8+
<p>This query helps to detect when a developer allocates an array or other fixed-length data structure such as <code>std::vector</code> with 365 elements – one for each day of the year.</p>
9+
<p>Since leap years have 366 days, there will be no allocated element on December 31st at the end of a leap year; which will lead to a buffer overflow on a leap year.</p>
10+
11+
</overview>
12+
<recommendation>
13+
<p>When allocating memory for storing elements for each day of the year, ensure that the correct number of elements are allocated.</p>
14+
<p>It is also highly recommended to compile the code with array-bounds checking enabled whenever possible.</p>
15+
16+
</recommendation>
17+
<example>
18+
<p>In this example, we are allocating 365 integers, one for each day of the year. This code will fail on a leap year, when there are 366 days.</p>
19+
<sample src="examples/UnsafeArrayForDaysOfYearBad.c" />
20+
21+
<p>When using arrays, allocate the correct number of elements to match the year.</p>
22+
<sample src="examples/UnsafeArrayForDaysOfYearGood.c" />
23+
</example>
24+
25+
<references>
26+
<li>NASA / Goddard Space Flight Center - <a href="https://eclipse.gsfc.nasa.gov/SEhelp/calendars.html">Calendars</a></li>
27+
<li>Wikipedia - <a href="https://en.wikipedia.org/wiki/Leap_year_bug"> Leap year bug</a></li>
28+
<li>Microsoft Azure blog - <a href="https://azure.microsoft.com/en-us/blog/is-your-code-ready-for-the-leap-year/"> Is your code ready for the leap year?</a> </li>
29+
</references>
30+
</qhelp>

0 commit comments

Comments
 (0)