|
| 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