diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.java new file mode 100644 index 0000000..fc1ef40 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx.rules.checkers; + +import com.sonar.cxx.sslr.api.AstNode; +import com.sonar.cxx.sslr.api.Grammar; +import org.apache.commons.lang.StringUtils; +import org.sonar.check.Priority; +import org.sonar.check.Rule; +import org.sonar.cxx.parser.CxxGrammarImpl; +import org.sonar.cxx.squidbridge.annotations.ActivatedByDefault; +import org.sonar.cxx.squidbridge.annotations.SqaleConstantRemediation; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +/** + * FormatFunctionCheck + * + * @author WuHaoYang + * @date 2024/1/24 + */ +@Rule(key = "FormatFunctionCheck", name = "确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数", description = "确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数", priority = Priority.INFO, tags = {"28suo"}) +@ActivatedByDefault +@SqaleConstantRemediation("5min") +public class FormatFunctionCheck extends SquidCheck { + + + //函数列表 + private static final List TARGET_FUNCS = Arrays.asList("printf", "sprintf", "snprintf", "fprintf", "format"); + + @Override + public void init() { + //节点类型 + subscribeTo(CxxGrammarImpl.postfixExpression); + } + + @Override + public void visitNode(AstNode astNode) { + if (isFunctionCall(astNode)) { + validateFunctionCall(astNode); + } + } + + private boolean isFunctionCall(AstNode astNode) { + AstNode potentialFunctionNameNode = astNode.getFirstDescendant(CxxGrammarImpl.className); + return potentialFunctionNameNode != null && TARGET_FUNCS.contains(potentialFunctionNameNode.getTokenValue()); + } + + private void validateFunctionCall(AstNode astNode) { + // 分别针对不同函数调用进行处理,替换原有的认证方法 + String functionName = astNode.getFirstDescendant(CxxGrammarImpl.className).getTokenValue(); + + if(functionName.equals("printf")){ + validatePrintfFunctionCall(astNode); + } else if (functionName.equals("sprintf")) { + validateSprintfFunctionCall(astNode); + } else if (functionName.equals("snprintf")) { + validateSnprintfFunctionCall(astNode); + } else if (functionName.equals("fprintf")) { + validateFprintfFunctionCall(astNode); + } else if (functionName.equals("format")) { + validateFormatFunctionCall(astNode); + } + + } + + + // 从AstNode节点获取参数列表 + private List getParameters(AstNode functionCallNode) { + // 获取所有子节点,包括是逗号(,)的 + List allChildren = functionCallNode.getFirstDescendant(CxxGrammarImpl.initializerList).getChildren(); + + // 创建一个新的列表来保存非逗号(,)的子节点,即参数 + List parameters = new ArrayList<>(); + for (AstNode child : allChildren) { + if (!child.getTokenValue().equals(",")) { + parameters.add(child); + } + } + + return parameters; + } + + private boolean isFunctionFormatStatic(AstNode functionCallNode) { + if (isFunctionCall(functionCallNode)) { + String functionName = functionCallNode.getFirstDescendant(CxxGrammarImpl.className).getTokenValue(); + + List parameters = getParameters(functionCallNode); + + if (TARGET_FUNCS.contains(functionName)) { + int argIndex; + if (functionName.equals("fprintf") || functionName.equals("sprintf")) { + argIndex = 1; + } else if (functionName.equals("snprintf")) { + argIndex = 2; + } else if (functionName.equals("format")) { + argIndex = 0; + } else { + argIndex = 0; + } + + if (parameters.size() > argIndex) { + AstNode formatArgNode = parameters.get(argIndex); + String potentialFormatStringParam = formatArgNode.getTokenValue(); + return potentialFormatStringParam.startsWith("\"") && potentialFormatStringParam.endsWith("\""); + } else { + } + } + } + return false; + } + + private boolean isParameterCountValid(AstNode functionCallNode) { + String functionName = functionCallNode.getFirstDescendant(CxxGrammarImpl.className).getTokenValue(); + + List parameters = getParameters(functionCallNode); + + if (TARGET_FUNCS.contains(functionName)) { + int argIndex; + if (functionName.equals("fprintf") || functionName.equals("sprintf")) { + argIndex = 1; + } else if (functionName.equals("snprintf")) { + argIndex = 2; + } else if (functionName.equals("format")) { + argIndex = 0; + } else { + argIndex = 0; + } + + if (parameters.size() <= argIndex) { + return false; + } + + String format = removeEscapedPercentage(parameters.get(argIndex).getTokenValue()); + format = format.substring(1, format.length() - 1); + + int expectedCount = StringUtils.countMatches(format, "%"); + if(functionName.equals("format")) { + expectedCount = StringUtils.countMatches(format, "{}"); + } + return expectedCount == parameters.size() - 1 - argIndex; + } + + return false; + } + + private String removeEscapedPercentage(String format){ + return format.replace("%%",""); + } + + private void validatePrintfFunctionCall(AstNode astNode) { + if (!isFunctionFormatStatic(astNode) || !isParameterCountValid(astNode)) { + getContext().createLineViolation(this, "确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数", astNode); + } + } + + private void validateSprintfFunctionCall(AstNode astNode) { + if (!isFunctionFormatStatic(astNode) || !isParameterCountValid(astNode)) { + getContext().createLineViolation(this, "确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数", astNode); + } + } + + private void validateSnprintfFunctionCall(AstNode astNode) { + if (!isFunctionFormatStatic(astNode) || !isParameterCountValid(astNode)) { + getContext().createLineViolation(this, "确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数", astNode); + } + } + + private void validateFprintfFunctionCall(AstNode astNode) { + if (!isFunctionFormatStatic(astNode) || !isParameterCountValid(astNode)) { + getContext().createLineViolation(this, "确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数", astNode); + } + } + + private void validateFormatFunctionCall(AstNode astNode) { + if (!isFunctionFormatStatic(astNode) || !isParameterCountValid(astNode)) { + getContext().createLineViolation(this, "确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数", astNode); + } + } +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheckTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheckTest.java new file mode 100644 index 0000000..96b2f90 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheckTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx.rules.checkers; + + +import com.keyware.sonar.cxx.CxxFileTesterHelper; +import org.junit.jupiter.api.Test; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.cxx.squidbridge.api.SourceFile; +import org.sonar.cxx.squidbridge.checks.CheckMessagesVerifier; + +import java.io.IOException; + +/** + * FormatFunctionCheckTest + * + * @author WuHaoYang + * @date 2024/1/24 + */ +public class FormatFunctionCheckTest { + + + @Test + public void checkTest() throws IOException { + var checker = new FormatFunctionCheck(); + var tester = CxxFileTesterHelper.create("FormatFunctionCheck.cc"); + SourceFile file = CxxAstScanner.scanSingleInputFile(tester.asInputFile(), checker); + CheckMessagesVerifier.verify(file.getCheckMessages()) + .next().atLine(6).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数") + .next().atLine(9).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数") + .next().atLine(12).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数") + .next().atLine(16).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数") + .next().atLine(20).withMessage("确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数") + .noMore(); + } +} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.cc new file mode 100644 index 0000000..259b7c4 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.cc @@ -0,0 +1,23 @@ + + +int main() +{ + const char* name = "World"; + printf("Hello, %s!\n", name,name1,name2); + + char buffer[50]; + sprintf(buffer,"Hello, %s!\n",name,name1,name2); + + char buffer[50]; + snprintf(buffer, sizeof(buffer), "Hello, %s!\n",name, name1, name2); + + FILE* fp = fopen("file.txt", "w"); + if(fp) { + fprintf(fp, "Hel1o, %s!\n", name, name1, name2); + fclose(fp); + } + + string str = format("Hello, {}!\n",name, name1, name2); + + return 0; +} \ No newline at end of file