Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1b8117b

Browse files
author
Denis Levin
committed
C++: Mishandling Japanese Era and Leap Year in calculations
1 parent 114ba0e commit 1b8117b

35 files changed

Lines changed: 1847 additions & 0 deletions
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
When eras change, date and time conversions that rely on hard-coded era start date need to be reviewed. Conversions relying on Japanese dates in the current era can produce an ambiguous date.
8+
</p>
9+
</overview>
10+
11+
<references>
12+
<li>
13+
<a href="https://blogs.msdn.microsoft.com/shawnste/2018/04/12/the-japanese-calendars-y2k-moment/">The Japanese Calendar’s Y2K Moment</a>.
14+
</li>
15+
</references>
16+
</qhelp>
17+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @name ConstructingJapaneseEraStartDate
3+
* @description Japanese era changes can lead to code behaving differently. Aviod hard-coding Japanese era start dates. The values should be read from registry.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id cpp/JapaneseEra/Constructor-Or-Method-With-Exact-Era-Date
7+
* @precision medium
8+
* @tags reliability
9+
* japanese-era
10+
*/
11+
12+
import cpp
13+
from Call cc, int i
14+
where cc.getArgument(i).getValue().toInt() = 1989 and
15+
cc.getArgument(i+1).getValue().toInt() = 1 and
16+
cc.getArgument(i+2).getValue().toInt() = 8
17+
select cc, "Call that appears to have hard-coded Japanese era start date as parameter"
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>
7+
When eras change, date and time conversions that rely on hard-coded era start date need to be reviewed. Conversions relying on Japanese dates in the current era can produce an ambiguous date.
8+
</p>
9+
</overview>
10+
11+
<references>
12+
<li>
13+
<a href="https://blogs.msdn.microsoft.com/shawnste/2018/04/12/the-japanese-calendars-y2k-moment/">The Japanese Calendar’s Y2K Moment</a>.
14+
</li>
15+
</references>
16+
</qhelp>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @name StructWithJapaneseEraStartDate
3+
* @description Japanese era changes can lead to code behaving differently. Aviod hard-coding Japanese era start dates. The values should be read from registry.
4+
* @kind problem
5+
* @problem.severity warning
6+
* @id cpp/JapaneseEra/Struct-With-Exact-Era-Date
7+
* @precision medium
8+
* @tags reliability
9+
* japanese-era
10+
*/
11+
12+
import cpp
13+
14+
import semmle.code.cpp.commons.DateTime
15+
16+
from StructLikeClass s, YearFieldAccess year, MonthFieldAccess month, DayFieldAccess day, Operation yearAssignment, Operation monthAssignment, Operation dayAssignment
17+
where s.getAField().getAnAccess () = year and yearAssignment.getAnOperand() = year and yearAssignment.getAnOperand().getValue().toInt() = 1989 and
18+
s.getAField().getAnAccess () = month and monthAssignment.getAnOperand() = month and monthAssignment.getAnOperand().getValue().toInt() = 1 and
19+
s.getAField().getAnAccess () = day and dayAssignment.getAnOperand() = day and dayAssignment.getAnOperand().getValue().toInt() = 8
20+
select year, "A time struct that is initialized with exact Japanese calendar era start date"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name Year field changed using an arithmetic operation is used on an unchekced time conversion function
3+
* @description A year field changed using an arithmetic operation is used on a time conversion function, but the return value of the function is not check for success or failure
4+
* @kind problem
5+
* @problem.severity error
6+
* @id cpp/leap-year/adding-365-days-per-year
7+
* @precision high
8+
* @tags security
9+
* leap-year
10+
*/
11+
12+
import cpp
13+
import LeapYear
14+
import semmle.code.cpp.dataflow.DataFlow
15+
16+
17+
from Expr source, Expr sink, PossibleYearArithmeticOperationCheckConfiguration config
18+
where config.hasFlow(DataFlow::exprNode(source), DataFlow::exprNode(sink))
19+
select sink, "This arithmetic operation $@ uses a constant value of 365 ends up modifying the date/time located at $@, without considering leap year scenarios."
20+
, source, source.toString()
21+
, sink, sink.toString()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
<overview>
6+
<p>The Gregorian Calendar, which has become the internationally accepted civil calendar, the leap year rule 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+
</overview>
10+
11+
<references>
12+
<li>U.S. Naval Observatory Website - <a href="https://aa.usno.navy.mil/faq/docs/calendars.php"> Introduction to Calendars</a></li>
13+
<li>Wikipedia - <a href="https://en.wikipedia.org/wiki/Leap_year_bug"> Leap year bug</a> </li>
14+
<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>
15+
</references>
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
/**
2+
* Provides a library for helping create leap year realted queries
3+
*/
4+
import cpp
5+
import semmle.code.cpp.dataflow.DataFlow
6+
import semmle.code.cpp.controlflow.Guards
7+
import semmle.code.cpp.commons.DateTime
8+
9+
/**
10+
* Get the top-level BinaryOperation enclosing the expression e
11+
* Not
12+
*/
13+
BinaryOperation getATopLevelBinaryOperationExpression(Expr e)
14+
{
15+
result = e.getEnclosingElement().(BinaryOperation)
16+
or
17+
result = getATopLevelBinaryOperationExpression( e.getEnclosingElement())
18+
}
19+
20+
/**
21+
* Holds if the top-level binary operation for expression `e` includes the operator specified in `operator` with an operand specified by `valueToCheck`
22+
*/
23+
predicate additionalLogicalCheck( Expr e, string operation, int valueToCheck) {
24+
exists(BinaryLogicalOperation bo |
25+
bo = getATopLevelBinaryOperationExpression(e) |
26+
exists( BinaryArithmeticOperation bao |
27+
bao = bo.getAChild*() |
28+
bao.getAnOperand().getValue().toInt() = valueToCheck
29+
and bao.getOperator() = operation
30+
)
31+
)
32+
}
33+
34+
/**
35+
* Operation that seems to be checking for leap year
36+
*/
37+
class CheckForLeapYearOperation extends Operation {
38+
CheckForLeapYearOperation() {
39+
exists( BinaryArithmeticOperation bo |
40+
bo = this |
41+
bo.getAnOperand().getValue().toInt() = 4
42+
and bo.getOperator().toString() = "%"
43+
and additionalLogicalCheck( this.getEnclosingElement(), "%", 100)
44+
and additionalLogicalCheck( this.getEnclosingElement(), "%", 400)
45+
)
46+
}
47+
48+
override string getOperator() { result = "LeapYearCheck" }
49+
}
50+
51+
/**
52+
* abstract class of type YearFieldAccess that would represent an access to a year field on a struct and is used for arguing about leap year calculations
53+
*/
54+
abstract class LeapYearFieldAccess extends YearFieldAccess {
55+
/**
56+
* Holds if the field access is a modification,
57+
* and it involves an arithmetic operation
58+
*/
59+
predicate isModifiedByArithmeticOperation() {
60+
this.isModified()
61+
and exists( Operation op |
62+
op.getAnOperand() = this
63+
and ( op instanceof AssignArithmeticOperation
64+
or exists( BinaryArithmeticOperation bao |
65+
bao = op.getAnOperand())
66+
or op instanceof PostfixCrementOperation
67+
or op instanceof PrefixCrementOperation
68+
)
69+
)
70+
}
71+
72+
/**
73+
* Holds if the field access is a modification,
74+
* and it involves an arithmetic operation.
75+
* In order to avoid false positives, the operation does not includes values that are normal for year normalization.
76+
*
77+
* 1900 - struct tm counts years since 1900
78+
* 1980/80 - FAT32 epoch
79+
*/
80+
predicate isModifiedByArithmeticOperationNotForNormalization() {
81+
this.isModified()
82+
and exists( Operation op |
83+
op.getAnOperand() = this
84+
and ( (op instanceof AssignArithmeticOperation
85+
and not ( op.getAChild().getValue().toInt() = 1900
86+
or op.getAChild().getValue().toInt() = 2000
87+
or op.getAChild().getValue().toInt() = 1980
88+
or op.getAChild().getValue().toInt() = 80
89+
// Special case for transforming marshaled 2-digit year date:
90+
// theTime.wYear += 100*value;
91+
or exists( MulExpr mulBy100 | mulBy100 = op.getAChild() |
92+
mulBy100.getAChild().getValue().toInt() = 100 )))
93+
or exists( BinaryArithmeticOperation bao |
94+
bao = op.getAnOperand()
95+
and not ( bao.getAChild().getValue().toInt() = 1900
96+
or bao.getAChild().getValue().toInt() = 2000
97+
or bao.getAChild().getValue().toInt() = 1980
98+
or bao.getAChild().getValue().toInt() = 80
99+
// Special case for transforming marshaled 2-digit year date:
100+
// theTime.wYear += 100*value;
101+
or exists( MulExpr mulBy100 | mulBy100 = op.getAChild() |
102+
mulBy100.getAChild().getValue().toInt() = 100 ))
103+
)
104+
or op instanceof PostfixCrementOperation
105+
or op instanceof PrefixCrementOperation
106+
)
107+
)
108+
}
109+
110+
111+
/**
112+
* Holds if the top-level binary operation includes a modulus operator with an operand specified by `valueToCheck`
113+
*/
114+
predicate additionalModulusCheckForLeapYear( int valueToCheck) {
115+
additionalLogicalCheck(this, "%", valueToCheck)
116+
}
117+
118+
/**
119+
* Holds if the top-level binary operation includes an addition or subtraction operator with an operand specified by `valueToCheck`
120+
*/
121+
predicate additionalAdditionOrSubstractionCheckForLeapYear( int valueToCheck) {
122+
additionalLogicalCheck(this, "+", valueToCheck)
123+
or additionalLogicalCheck(this, "-", valueToCheck)
124+
}
125+
126+
/**
127+
* Holds true if this object is used on a modulus 4 operation, which would likely indicate the start of a leap year check
128+
*/
129+
predicate isUsedInMod4Operation()
130+
{
131+
not this.isModified() and
132+
exists( BinaryArithmeticOperation bo |
133+
bo.getAnOperand() = this
134+
and bo.getAnOperand().getValue().toInt() = 4
135+
and bo.getOperator().toString() = "%"
136+
)
137+
}
138+
139+
/**
140+
* Holds true if this object seems to be used in a valid gregorian calendar leap year check
141+
*/
142+
predicate isUsedInCorrectLeapYearCheck()
143+
{
144+
// The Gregorian leap year rule is:
145+
// Every year that is exactly divisible by four is a leap year,
146+
// except for years that are exactly divisible by 100,
147+
// but these centurial years are leap years if they are exactly divisible by 400
148+
//
149+
// https://aa.usno.navy.mil/faq/docs/calendars.php
150+
this.isUsedInMod4Operation()
151+
and additionalModulusCheckForLeapYear(400)
152+
and additionalModulusCheckForLeapYear(100)
153+
}
154+
}
155+
156+
/**
157+
* YearFieldAccess for SYSTEMTIME struct
158+
*/
159+
class StructSystemTimeLeapYearFieldAccess extends LeapYearFieldAccess {
160+
StructSystemTimeLeapYearFieldAccess() {
161+
this.toString().matches("wYear")
162+
}
163+
}
164+
/**
165+
* YearFieldAccess for struct tm
166+
*/
167+
class StructTmLeapYearFieldAccess extends LeapYearFieldAccess {
168+
StructTmLeapYearFieldAccess() {
169+
this.toString().matches("tm_year")
170+
}
171+
172+
override predicate isUsedInCorrectLeapYearCheck()
173+
{
174+
this.isUsedInMod4Operation()
175+
and additionalModulusCheckForLeapYear(400)
176+
and additionalModulusCheckForLeapYear(100)
177+
// tm_year represents years since 1900
178+
and ( additionalAdditionOrSubstractionCheckForLeapYear(1900)
179+
// some systems may use 2000 for 2-digit year conversions
180+
or additionalAdditionOrSubstractionCheckForLeapYear(2000)
181+
// converting from/to Unix epoch
182+
or additionalAdditionOrSubstractionCheckForLeapYear(1970)
183+
)
184+
}
185+
}
186+
187+
/**
188+
* FunctionCall that includes an operation that is checking for leap year
189+
*/
190+
class ChecksForLeapYearFunctionCall extends FunctionCall {
191+
ChecksForLeapYearFunctionCall() {
192+
exists( Function f, CheckForLeapYearOperation clyo |
193+
f.getACallToThisFunction() = this |
194+
clyo.getEnclosingFunction() = f
195+
)
196+
}
197+
}
198+
199+
/**
200+
* DataFlow::Configuration for finding a variable access that would flow into
201+
* a function call that includes an operation to check for leap year
202+
*/
203+
class LeapYearCheckConfiguration extends DataFlow::Configuration {
204+
LeapYearCheckConfiguration() {
205+
this = "LeapYearCheckConfiguration"
206+
}
207+
208+
override predicate isSource(DataFlow::Node source) {
209+
exists( VariableAccess va |
210+
va = source.asExpr()
211+
)
212+
}
213+
214+
override predicate isSink(DataFlow::Node sink) {
215+
exists( ChecksForLeapYearFunctionCall fc |
216+
sink.asExpr() = fc.getAnArgument()
217+
)
218+
}
219+
}
220+
221+
222+
/**
223+
* DataFlow::Configuration for finding an operation w/hardcoded 365 that will flow into a FILEINFO field
224+
*/
225+
class FiletimeYearArithmeticOperationCheckConfiguration extends DataFlow::Configuration {
226+
FiletimeYearArithmeticOperationCheckConfiguration() {
227+
this = "FiletimeYearArithmeticOperationCheckConfiguration"
228+
}
229+
230+
override predicate isSource(DataFlow::Node source) {
231+
exists( Expr e, Operation op |
232+
e = source.asExpr() |
233+
op.getAChild*().getValue().toInt()=365
234+
and op.getAChild*() = e
235+
)
236+
}
237+
238+
override predicate isSink(DataFlow::Node sink) {
239+
exists( StructLikeClass dds, FieldAccess fa, AssignExpr aexpr, Expr e |
240+
e = sink.asExpr() |
241+
dds instanceof FileTimeStruct
242+
and fa.getQualifier().getUnderlyingType() = dds
243+
and fa.isModified()
244+
and aexpr.getAChild() = fa
245+
and aexpr.getChild(1).getAChild*() = e
246+
)
247+
}
248+
}
249+
250+
/**
251+
* DataFlow::Configuration for finding an operation w/hardcoded 365 that will flow into any known date/time field
252+
*/
253+
class PossibleYearArithmeticOperationCheckConfiguration extends DataFlow::Configuration {
254+
PossibleYearArithmeticOperationCheckConfiguration() {
255+
this = "PossibleYearArithmeticOperationCheckConfiguration"
256+
}
257+
258+
override predicate isSource(DataFlow::Node source) {
259+
exists( Expr e, Operation op |
260+
e = source.asExpr() |
261+
op.getAChild*().getValue().toInt()=365
262+
and op.getAChild*() = e
263+
)
264+
}
265+
266+
override predicate isSink(DataFlow::Node sink) {
267+
exists( StructLikeClass dds, FieldAccess fa, AssignExpr aexpr, Expr e |
268+
e = sink.asExpr() |
269+
(dds instanceof FileTimeStruct
270+
or dds instanceof DateDataStruct)
271+
and fa.getQualifier().getUnderlyingType() = dds
272+
and fa.isModified()
273+
and aexpr.getAChild() = fa
274+
and aexpr.getChild(1).getAChild*() = e
275+
)
276+
}
277+
}

0 commit comments

Comments
 (0)