-
Notifications
You must be signed in to change notification settings - Fork 2k
CPP: Add a query to find incorrectly used switch #6081
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
Changes from all commits
bdab785
4f2703e
bf65044
1cabaec
94bd2a3
02bf800
4083da3
5c71a7c
80eb490
098773d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| ... | ||
| int i1; | ||
| char c1; | ||
| ... | ||
| if((c1<50)&&(c>10)) | ||
| switch(c1){ | ||
| case 300: // BAD: the code will not be executed | ||
| ... | ||
| if((i1<5)&&(i1>0)) | ||
| switch(i1){ // BAD | ||
| case 21: // BAD: the code will not be executed | ||
| ... | ||
| switch(c1){ | ||
| ... | ||
| dafault: // BAD: maybe it will be right `default` | ||
| ... | ||
| } | ||
|
|
||
| ... | ||
| switch(c1){ | ||
| i1=c1*2; // BAD: the code will not be executed | ||
| case 12: | ||
| ... | ||
| switch(c1){ // GOOD | ||
| case 12: | ||
| break; | ||
| case 10: | ||
| break; | ||
| case 9: | ||
| break; | ||
| default: | ||
| break; | ||
| } | ||
| ... |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| <!DOCTYPE qhelp PUBLIC | ||
| "-//Semmle//qhelp//EN" | ||
| "qhelp.dtd"> | ||
| <qhelp> | ||
| <overview> | ||
| <p>A mismatch between conditionals and <code>switch</code> cases can lead to control-flow violations (CWE-691) where the developer either does not handle all combinations of conditions or unintentionally created dead code (CWE-561).</p> | ||
|
|
||
|
|
||
| </overview> | ||
|
|
||
| <example> | ||
| <p>The following example demonstrates fallacious and fixed ways of using a <code>switch</code> statement.</p> | ||
| <sample src="FindIncorrectlyUsedSwitch.c" /> | ||
|
|
||
| </example> | ||
| <references> | ||
|
|
||
| <li> | ||
| CERT C Coding Standard: | ||
| <a href="https://wiki.sei.cmu.edu/confluence/display/c/MSC12-C.+Detect+and+remove+code+that+has+no+effect+or+is+never+executed">MSC12-C. Detect and remove code that has no effect or is never executed</a>. | ||
| </li> | ||
|
|
||
| </references> | ||
| </qhelp> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| /** | ||
| * @name Incorrect switch statement | ||
| * @description --Finding places the dangerous use of a switch. | ||
| * --For example, when the range of values for a condition does not cover all of the selection values.. | ||
| * @kind problem | ||
| * @id cpp/operator-find-incorrectly-used-switch | ||
| * @problem.severity warning | ||
| * @precision medium | ||
| * @tags correctness | ||
| * security | ||
| * external/cwe/cwe-561 | ||
| * external/cwe/cwe-691 | ||
| * external/cwe/cwe-478 | ||
| */ | ||
|
|
||
| import cpp | ||
| import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis | ||
|
|
||
| /** Holds if the range contains no boundary values. */ | ||
| predicate isRealRange(Expr exp) { | ||
| upperBound(exp).toString() != "18446744073709551616" and | ||
| upperBound(exp).toString() != "9223372036854775807" and | ||
| upperBound(exp).toString() != "4294967295" and | ||
| upperBound(exp).toString() != "Infinity" and | ||
| upperBound(exp).toString() != "NaN" and | ||
| lowerBound(exp).toString() != "-9223372036854775808" and | ||
| lowerBound(exp).toString() != "-4294967296" and | ||
| lowerBound(exp).toString() != "-Infinity" and | ||
| lowerBound(exp).toString() != "NaN" and | ||
| upperBound(exp) != 2147483647 and | ||
| upperBound(exp) != 268435455 and | ||
| upperBound(exp) != 33554431 and | ||
| upperBound(exp) != 8388607 and | ||
| upperBound(exp) != 65535 and | ||
| upperBound(exp) != 32767 and | ||
| upperBound(exp) != 255 and | ||
| upperBound(exp) != 127 and | ||
| upperBound(exp) != 63 and | ||
| upperBound(exp) != 31 and | ||
| upperBound(exp) != 15 and | ||
| upperBound(exp) != 7 and | ||
| lowerBound(exp) != -2147483648 and | ||
| lowerBound(exp) != -268435456 and | ||
| lowerBound(exp) != -33554432 and | ||
| lowerBound(exp) != -8388608 and | ||
| lowerBound(exp) != -65536 and | ||
| lowerBound(exp) != -32768 and | ||
| lowerBound(exp) != -128 | ||
| } | ||
|
|
||
| /** Holds if the range of values for the condition is less than the choices. */ | ||
| predicate isNotAllSelected(SwitchStmt swtmp) { | ||
| not swtmp.getExpr().isConstant() and | ||
| exists(int i | | ||
| i != 0 and | ||
| ( | ||
| i = lowerBound(swtmp.getASwitchCase().getExpr()) and | ||
| upperBound(swtmp.getExpr()) < i | ||
| or | ||
| ( | ||
| i = upperBound(swtmp.getASwitchCase().getExpr()) or | ||
| i = upperBound(swtmp.getASwitchCase().getEndExpr()) | ||
| ) and | ||
| lowerBound(swtmp.getExpr()) > i | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| /** Holds if the range of values for the condition is greater than the selection. */ | ||
| predicate isConditionBig(SwitchStmt swtmp) { | ||
| not swtmp.hasDefaultCase() and | ||
| not exists(int iu, int il | | ||
| ( | ||
| iu = upperBound(swtmp.getASwitchCase().getExpr()) or | ||
| iu = upperBound(swtmp.getASwitchCase().getEndExpr()) | ||
| ) and | ||
| upperBound(swtmp.getExpr()) = iu and | ||
| ( | ||
| il = lowerBound(swtmp.getASwitchCase().getExpr()) or | ||
| il = lowerBound(swtmp.getASwitchCase().getEndExpr()) | ||
| ) and | ||
| lowerBound(swtmp.getExpr()) = il | ||
| ) | ||
| } | ||
|
|
||
| /** Holds if there are labels inside the block with names similar to `default` or `case`. */ | ||
| predicate isWrongLableName(SwitchStmt swtmp) { | ||
| not swtmp.hasDefaultCase() and | ||
| exists(LabelStmt lb | | ||
| ( | ||
| ( | ||
| lb.getName().charAt(0) = "d" or | ||
| lb.getName().charAt(0) = "c" | ||
| ) and | ||
| ( | ||
| lb.getName().charAt(1) = "e" or | ||
| lb.getName().charAt(1) = "a" | ||
| ) and | ||
| ( | ||
| lb.getName().charAt(2) = "f" or | ||
| lb.getName().charAt(2) = "s" | ||
| ) | ||
|
Comment on lines
+91
to
+102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will match labels with the names "def", "des", "daf", "das", "cef", "ces", "caf", "cas". Is that the intended effect? Would it be better to flag all
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was hoping to find all the labels that start with these characters.
your offer is much better from a security point of view. it will also cover situations when you forgot to write
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good day. my analysis shows that unused labels are the result of constructs using complex transitions such as or a macro that generates various labels or the result of unfortunately in all these situations, the developer assumes the presence of unmanaged labels. I want to find a label similar to which is possible and is used in the transition operator (or maybe not), if you do not like the current code, I suggest changing it to detect specific erroneous labels,
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
To filter out such labels you could require that there doesn't exist a I admit those labels are all quite... intricate... I don't particularly mind this solution. Have you found any bugs caused by a misspelled
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. at the initial stage of the formation of the request, I found two ambiguous situations of using labels. but this did not look like an example of an error. |
||
| ) and | ||
| lb.getEnclosingStmt().getParentStmt*() = swtmp.getStmt() and | ||
| not exists(GotoStmt gs | gs.getName() = lb.getName()) | ||
| ) | ||
| } | ||
|
|
||
| /** Holds if the block contains code before the first `case`. */ | ||
| predicate isCodeBeforeCase(SwitchStmt swtmp) { | ||
| exists(Expr exp | | ||
| exp.getEnclosingStmt().getParentStmt*() = swtmp.getStmt() and | ||
| not exists(Loop lp | | ||
| exp.getEnclosingStmt().getParentStmt*() = lp and | ||
| lp.getEnclosingStmt().getParentStmt*() = swtmp.getStmt() | ||
| ) and | ||
| not exists(Stmt sttmp, SwitchCase sctmp | | ||
| sttmp = swtmp.getASwitchCase().getAStmt() and | ||
| sctmp = swtmp.getASwitchCase() and | ||
| ( | ||
| exp.getEnclosingStmt().getParentStmt*() = sttmp or | ||
| exp.getEnclosingStmt() = sctmp | ||
| ) | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| from SwitchStmt sw, string msg | ||
| where | ||
| isRealRange(sw.getExpr()) and | ||
| lowerBound(sw.getExpr()) != upperBound(sw.getExpr()) and | ||
| lowerBound(sw.getExpr()) != 0 and | ||
| not exists(Expr cexp | | ||
| cexp = sw.getASwitchCase().getExpr() and not isRealRange(cexp) | ||
| or | ||
| cexp = sw.getASwitchCase().getEndExpr() and not isRealRange(cexp) | ||
| ) and | ||
| not exists(Expr exptmp | | ||
| exptmp = sw.getExpr().getAChild*() and | ||
| not exptmp.isConstant() and | ||
| not isRealRange(exptmp) | ||
| ) and | ||
| (sw.getASwitchCase().terminatesInBreakStmt() or sw.getASwitchCase().terminatesInReturnStmt()) and | ||
| ( | ||
| isNotAllSelected(sw) and msg = "The range of condition values is less than the selection." | ||
| or | ||
| isConditionBig(sw) and msg = "The range of condition values is wider than the choices." | ||
| ) | ||
| or | ||
| isWrongLableName(sw) and msg = "Possibly erroneous label name." | ||
| or | ||
| isCodeBeforeCase(sw) and msg = "Code before case will not be executed." | ||
| select sw, msg | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| | test.c:20:3:28:3 | switch (...) ... | Possibly erroneous label name. | | ||
| | test.c:30:3:38:3 | switch (...) ... | Code before case will not be executed. | | ||
| | test.c:41:3:50:3 | switch (...) ... | The range of condition values is less than the selection. | | ||
| | test.c:53:3:58:3 | switch (...) ... | The range of condition values is wider than the choices. | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| experimental/Security/CWE/CWE-561/FindIncorrectlyUsedSwitch.ql |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| void testFunction(char c1,int i1) | ||
| { | ||
|
|
||
| switch(c1){ // GOOD | ||
|
ihsinme marked this conversation as resolved.
|
||
| case 12: | ||
| break; | ||
| case 10: | ||
| break; | ||
| case 9: | ||
| break; | ||
| } | ||
|
|
||
| switch(i1){ // GOOD | ||
| for(i1=0;i1<20;i1++){ | ||
| case 12: | ||
| case 10: | ||
| case 9: | ||
| } | ||
| } | ||
| switch(c1){ // BAD | ||
| case 12: | ||
| break; | ||
| case 10: | ||
| break; | ||
| case 9: | ||
| break; | ||
| dafault: | ||
| } | ||
|
|
||
| switch(c1){ // BAD | ||
| c1=c1*2; | ||
| case 12: | ||
| break; | ||
| case 10: | ||
| break; | ||
| case 9: | ||
| break; | ||
| } | ||
|
|
||
| if((c1<6)&&(c1>0)) | ||
| switch(c1){ // BAD | ||
| case 8: | ||
| break; | ||
| case 5: | ||
| break; | ||
| case 3: | ||
| break; | ||
| case 1: | ||
| break; | ||
| } | ||
|
|
||
| if((c1<6)&&(c1>0)) | ||
| switch(c1){ // BAD | ||
| case 3: | ||
| break; | ||
| case 1: | ||
| break; | ||
| } | ||
|
|
||
| } | ||
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.
Why exclude
0here?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.
0is also excluded inisConditionBigpredicate through significant range. this is done in order to exclude quite frequent stylistic situations before processing, when the developer processes0, and in theswitchblock either excludes the selection, or truncates the values of the conditions. here is an example from trimming selection.aappleby/smhasher#91