新增准则:确保向所有格式字符串函数都传递一个不能由用户控制的静态格式化字符串,并且向该函数发送正确数量的参数。

wuhaoyang
wuhaoyang 10 months ago
parent ab4ae0f844
commit 13bdd79c5b
  1. 188
      sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.java
  2. 40
      sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheckTest.java
  3. 23
      sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/rules/checkers/FormatFunctionCheck.cc

@ -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<Grammar> {
//函数列表
private static final List<String> 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<AstNode> getParameters(AstNode functionCallNode) {
// 获取所有子节点,包括是逗号(,)的
List<AstNode> allChildren = functionCallNode.getFirstDescendant(CxxGrammarImpl.initializerList).getChildren();
// 创建一个新的列表来保存非逗号(,)的子节点,即参数
List<AstNode> 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<AstNode> 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<AstNode> 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);
}
}
}

@ -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();
}
}

@ -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;
}
Loading…
Cancel
Save