diff --git a/engine/src/main/java/com/ibm/engine/language/LanguageSupporter.java b/engine/src/main/java/com/ibm/engine/language/LanguageSupporter.java index c5a82a55a..be1f0d367 100644 --- a/engine/src/main/java/com/ibm/engine/language/LanguageSupporter.java +++ b/engine/src/main/java/com/ibm/engine/language/LanguageSupporter.java @@ -19,6 +19,11 @@ */ package com.ibm.engine.language; +import com.ibm.engine.language.c.CCheck; +import com.ibm.engine.language.c.CLanguageSupport; +import com.ibm.engine.language.c.CScanContext; +import com.ibm.engine.language.c.CSymbol; +import com.ibm.engine.language.c.tree.CTree; import com.ibm.engine.language.csharp.CSharpCheck; import com.ibm.engine.language.csharp.CSharpLanguageSupport; import com.ibm.engine.language.csharp.CSharpScanContext; @@ -73,4 +78,9 @@ public static ILanguageSupport goLanguageS csharpLanguageSupporter() { return new CSharpLanguageSupport(); } + + @Nonnull + public static ILanguageSupport cLanguageSupporter() { + return new CLanguageSupport(); + } } diff --git a/engine/src/main/java/com/ibm/engine/language/c/CBaseMethodVisitor.java b/engine/src/main/java/com/ibm/engine/language/c/CBaseMethodVisitor.java new file mode 100644 index 000000000..2331185bb --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/CBaseMethodVisitor.java @@ -0,0 +1,53 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c; + +import com.ibm.engine.detection.IBaseMethodVisitor; +import com.ibm.engine.detection.IDetectionEngine; +import com.ibm.engine.detection.TraceSymbol; +import com.ibm.engine.language.c.tree.CBlockTree; +import com.ibm.engine.language.c.tree.CTree; +import javax.annotation.Nonnull; + +/** + * Base method visitor for C/C++ that invokes the detection engine on each function body. + * + *

Mirrors {@code CSharpBaseMethodVisitor}: when the sensor dispatches a function body (a {@link + * CBlockTree}) via {@link #visitMethodDefinition}, the detection engine is run on it. + */ +public final class CBaseMethodVisitor implements IBaseMethodVisitor { + + @Nonnull private final TraceSymbol traceSymbol; + @Nonnull private final IDetectionEngine detectionEngine; + + public CBaseMethodVisitor( + @Nonnull TraceSymbol traceSymbol, + @Nonnull IDetectionEngine detectionEngine) { + this.traceSymbol = traceSymbol; + this.detectionEngine = detectionEngine; + } + + @Override + public void visitMethodDefinition(@Nonnull CTree method) { + if (method instanceof CBlockTree blockTree) { + detectionEngine.run(traceSymbol, blockTree); + } + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/CCheck.java b/engine/src/main/java/com/ibm/engine/language/c/CCheck.java new file mode 100644 index 000000000..1c5d2805e --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/CCheck.java @@ -0,0 +1,34 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c; + +import com.ibm.engine.language.c.tree.CBlockTree; +import javax.annotation.Nonnull; + +/** + * Marker interface for C/C++ cryptography detection checks. + * + *

Since there is no SonarQube-native C/C++ custom rule API, a custom sensor calls {@link #scan} + * directly for every function body it encounters during parsing. + */ +public interface CCheck { + + void scan(@Nonnull CScanContext scanContext, @Nonnull CBlockTree blockTree); +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/CDetectionEngine.java b/engine/src/main/java/com/ibm/engine/language/c/CDetectionEngine.java new file mode 100644 index 000000000..0181bcf13 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/CDetectionEngine.java @@ -0,0 +1,335 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.Handler; +import com.ibm.engine.detection.IDetectionEngine; +import com.ibm.engine.detection.MethodDetection; +import com.ibm.engine.detection.ResolvedValue; +import com.ibm.engine.detection.TraceSymbol; +import com.ibm.engine.detection.ValueDetection; +import com.ibm.engine.language.c.tree.CBlockTree; +import com.ibm.engine.language.c.tree.CFunctionCallTree; +import com.ibm.engine.language.c.tree.CIdentifierTree; +import com.ibm.engine.language.c.tree.CLiteralTree; +import com.ibm.engine.language.c.tree.CTree; +import com.ibm.engine.model.factory.IValueFactory; +import com.ibm.engine.rule.DetectableParameter; +import com.ibm.engine.rule.DetectionRule; +import com.ibm.engine.rule.MethodDetectionRule; +import com.ibm.engine.rule.Parameter; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Detection engine implementation for C/C++. + * + *

Walks a {@link CBlockTree} looking for {@link CFunctionCallTree} nodes that match the active + * detection rule, then emits detections and resolves argument values. + * + *

Symbol resolution is intentionally minimal: only literal values ({@link CLiteralTree}) and + * direct identifiers ({@link CIdentifierTree}) are resolved. + */ +@SuppressWarnings("java:S3776") +public final class CDetectionEngine implements IDetectionEngine { + + @Nonnull + private final DetectionStore detectionStore; + + @Nonnull private final Handler handler; + + public CDetectionEngine( + @Nonnull DetectionStore detectionStore, + @Nonnull Handler handler) { + this.detectionStore = detectionStore; + this.handler = handler; + } + + @Override + public void run(@Nonnull CTree tree) { + run(TraceSymbol.createStart(), tree); + } + + @Override + public void run(@Nonnull TraceSymbol traceSymbol, @Nonnull CTree tree) { + if (tree instanceof CBlockTree blockTree) { + for (CTree statement : blockTree.getStatements()) { + processStatement(traceSymbol, statement); + } + } else if (tree instanceof CFunctionCallTree call) { + if (traceSymbol.is(TraceSymbol.State.SYMBOL) + && !isInvocationOnVariable(call, traceSymbol)) { + return; + } + handler.addCallToCallStack(call, detectionStore.getScanContext()); + if (detectionStore + .getDetectionRule() + .match(call, handler.getLanguageSupport().translation())) { + analyseFunctionCall(call); + } + } + } + + private void processStatement( + @Nonnull TraceSymbol traceSymbol, @Nonnull CTree statement) { + if (statement instanceof CFunctionCallTree call) { + if (traceSymbol.is(TraceSymbol.State.SYMBOL) + && !isInvocationOnVariable(call, traceSymbol)) { + return; + } + handler.addCallToCallStack(call, detectionStore.getScanContext()); + if (detectionStore + .getDetectionRule() + .match(call, handler.getLanguageSupport().translation())) { + analyseFunctionCall(call); + } + } + } + + // ------------------------------------------------------------------------- + // Function call analysis + // ------------------------------------------------------------------------- + + private void analyseFunctionCall(@Nonnull CFunctionCallTree call) { + DetectionRule rule = emitDetectionAndGetRule(call); + if (rule == null) { + return; + } + List arguments = call.getArguments(); + processParameters(rule.parameters(), arguments, call); + } + + @SuppressWarnings("unchecked") + @Nullable private DetectionRule emitDetectionAndGetRule(@Nonnull CTree tree) { + if (detectionStore.getDetectionRule().is(MethodDetectionRule.class)) { + detectionStore.onReceivingNewDetection(new MethodDetection<>(tree, null)); + return null; + } + DetectionRule detectionRule = + (DetectionRule) detectionStore.getDetectionRule(); + if (detectionRule.actionFactory() != null) { + detectionStore.onReceivingNewDetection(new MethodDetection<>(tree, null)); + } + return detectionRule; + } + + private void processParameters( + @Nonnull List> parameters, + @Nonnull List arguments, + @Nonnull CTree parentTree) { + int index = 0; + for (Parameter parameter : parameters) { + if (index >= arguments.size()) { + break; + } + processParameter(parameter, arguments.get(index), parentTree); + index++; + } + } + + @SuppressWarnings("unchecked") + private void processParameter( + @Nonnull Parameter parameter, + @Nonnull CTree expression, + @Nonnull CTree parentTree) { + if (parameter.is(DetectableParameter.class)) { + DetectableParameter detectable = (DetectableParameter) parameter; + List> resolved = + resolveValuesInInnerScope( + Object.class, expression, detectable.getiValueFactory()); + if (resolved.isEmpty()) { + resolveValuesInOuterScope(expression, detectable); + } else { + resolved.stream() + .map(rv -> new ValueDetection<>(rv, detectable, parentTree, parentTree)) + .forEach(detectionStore::onReceivingNewDetection); + } + } else if (!parameter.getDetectionRules().isEmpty()) { + detectionStore.onDetectedDependingParameter( + parameter, expression, DetectionStore.Scope.EXPRESSION); + } + } + + // ------------------------------------------------------------------------- + // Value resolution + // ------------------------------------------------------------------------- + + @Nonnull + @Override + public List> resolveValuesInInnerScope( + @Nonnull Class clazz, + @Nonnull CTree expression, + @Nullable IValueFactory valueFactory) { + return resolveValues(clazz, expression); + } + + @Nonnull + @SuppressWarnings({"unchecked"}) + private List> resolveValues( + @Nonnull Class clazz, @Nonnull CTree tree) { + if (tree instanceof CLiteralTree literal) { + String value = literal.getValue(); + Optional resolved = resolveConstant(clazz, value); + return resolved.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + if (tree instanceof CIdentifierTree identifier) { + Optional resolved = resolveConstant(clazz, identifier.getName()); + return resolved.map(v -> List.of(new ResolvedValue<>(v, tree))) + .orElse(Collections.emptyList()); + } + + return Collections.emptyList(); + } + + @Nonnull + @SuppressWarnings("unchecked") + private Optional resolveConstant(@Nonnull Class clazz, @Nullable String value) { + if (value == null) { + return Optional.empty(); + } + try { + if (clazz == String.class) { + return Optional.of(clazz.cast(value)); + } + if (clazz == Integer.class || clazz == Object.class) { + try { + Integer intValue = Integer.parseInt(value); + if (clazz == Integer.class) { + return Optional.of(clazz.cast(intValue)); + } + return Optional.of((O) intValue); + } catch (NumberFormatException e) { + // not a number + } + } + if (clazz == Object.class) { + return Optional.of((O) value); + } + return Optional.empty(); + } catch (ClassCastException e) { + return Optional.empty(); + } + } + + @Override + public void resolveValuesInOuterScope( + @Nonnull CTree expression, @Nonnull Parameter parameter) { + // Cross-scope resolution not supported without semantic analysis + } + + @Override + public void resolveMethodReturnValues( + @Nonnull Class clazz, + @Nonnull CTree methodDefinition, + @Nonnull Parameter parameter) { + // Not supported without type inference + } + + @Nullable @Override + public ResolvedValue resolveEnumValue( + @Nonnull Class clazz, + @Nonnull CTree enumClassDefinition, + @Nonnull LinkedList selections) { + return null; + } + + // ------------------------------------------------------------------------- + // Symbol tracking + // ------------------------------------------------------------------------- + + @Nonnull + @Override + public Optional> getAssignedSymbol(@Nonnull CTree expression) { + if (expression instanceof CFunctionCallTree call) { + String assigned = call.getAssignedIdentifier(); + if (assigned != null) { + return Optional.of(TraceSymbol.createFrom(new CSymbol(assigned))); + } + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional> getMethodInvocationParameterSymbol( + @Nonnull CTree methodInvocation, @Nonnull Parameter parameter) { + if (methodInvocation instanceof CFunctionCallTree call) { + List args = call.getArguments(); + int idx = parameter.getIndex(); + if (idx >= 0 && idx < args.size()) { + return Optional.of(TraceSymbol.createWithStateNoSymbol()); + } + return Optional.of(TraceSymbol.createWithStateDifferent()); + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional> getNewClassParameterSymbol( + @Nonnull CTree newClass, @Nonnull Parameter parameter) { + return getMethodInvocationParameterSymbol(newClass, parameter); + } + + @Override + public boolean isInvocationOnVariable( + CTree methodInvocation, @Nonnull TraceSymbol variableSymbol) { + if (!(methodInvocation instanceof CFunctionCallTree call)) { + return false; + } + CSymbol sym = variableSymbol.getSymbol(); + if (sym == null) { + return false; + } + return call.getObjectTypeName().equals(sym.getName()); + } + + @Override + public boolean isInitForVariable( + CTree newClass, @Nonnull TraceSymbol variableSymbol) { + if (!(newClass instanceof CFunctionCallTree call)) { + return false; + } + String assignedId = call.getAssignedIdentifier(); + if (assignedId == null) { + return false; + } + CSymbol sym = variableSymbol.getSymbol(); + if (sym == null) { + return false; + } + return assignedId.equals(sym.getName()); + } + + @Nullable @Override + public CTree extractArgumentFromMethodCaller( + @Nonnull CTree methodDefinition, + @Nonnull CTree methodInvocation, + @Nonnull CTree methodParameterIdentifier) { + return null; + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/CLanguageSupport.java b/engine/src/main/java/com/ibm/engine/language/c/CLanguageSupport.java new file mode 100644 index 000000000..56aa95cd4 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/CLanguageSupport.java @@ -0,0 +1,108 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c; + +import com.ibm.engine.detection.DetectionStore; +import com.ibm.engine.detection.EnumMatcher; +import com.ibm.engine.detection.Handler; +import com.ibm.engine.detection.IBaseMethodVisitorFactory; +import com.ibm.engine.detection.IDetectionEngine; +import com.ibm.engine.detection.MatchContext; +import com.ibm.engine.detection.MethodMatcher; +import com.ibm.engine.executive.DetectionExecutive; +import com.ibm.engine.language.ILanguageSupport; +import com.ibm.engine.language.ILanguageTranslation; +import com.ibm.engine.language.IScanContext; +import com.ibm.engine.language.c.tree.CFunctionCallTree; +import com.ibm.engine.language.c.tree.CTree; +import com.ibm.engine.rule.IDetectionRule; +import java.util.Optional; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Language support implementation for C/C++. + * + *

Mirrors {@code CSharpLanguageSupport}: wires together the {@link CLanguageTranslation}, {@link + * CDetectionEngine}, and {@link CBaseMethodVisitor} using the shared {@link Handler} and {@link + * DetectionExecutive} infrastructure. + */ +public final class CLanguageSupport + implements ILanguageSupport { + + @Nonnull private final Handler handler; + + @Nonnull private final CLanguageTranslation translation; + + public CLanguageSupport() { + this.handler = new Handler<>(this); + this.translation = new CLanguageTranslation(); + } + + @Nonnull + @Override + public ILanguageTranslation translation() { + return translation; + } + + @Nonnull + @Override + public DetectionExecutive createDetectionExecutive( + @Nonnull CTree tree, + @Nonnull IDetectionRule detectionRule, + @Nonnull IScanContext scanContext) { + return new DetectionExecutive<>(tree, detectionRule, scanContext, this.handler); + } + + @Nonnull + @Override + public IDetectionEngine createDetectionEngineInstance( + @Nonnull DetectionStore detectionStore) { + return new CDetectionEngine(detectionStore, this.handler); + } + + @Nonnull + @Override + public IBaseMethodVisitorFactory getBaseMethodVisitorFactory() { + return CBaseMethodVisitor::new; + } + + @Nonnull + @Override + public Optional getEnclosingMethod(@Nonnull CTree expression) { + if (expression instanceof CFunctionCallTree call && call.getEnclosingBlock() != null) { + return Optional.of(call.getEnclosingBlock()); + } + return Optional.empty(); + } + + @Nullable @Override + public MethodMatcher createMethodMatcherBasedOn(@Nonnull CTree methodDefinition) { + // Inter-procedural method matching not supported without semantic analysis + return null; + } + + @Nullable @Override + public EnumMatcher createSimpleEnumMatcherFor( + @Nonnull CTree enumIdentifier, @Nonnull MatchContext matchContext) { + Optional name = translation().getEnumIdentifierName(matchContext, enumIdentifier); + return name.>map(EnumMatcher::new).orElse(null); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/CLanguageTranslation.java b/engine/src/main/java/com/ibm/engine/language/c/CLanguageTranslation.java new file mode 100644 index 000000000..3a274fd77 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/CLanguageTranslation.java @@ -0,0 +1,122 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c; + +import com.ibm.engine.detection.IType; +import com.ibm.engine.detection.MatchContext; +import com.ibm.engine.language.ILanguageTranslation; +import com.ibm.engine.language.c.tree.CFunctionCallTree; +import com.ibm.engine.language.c.tree.CIdentifierTree; +import com.ibm.engine.language.c.tree.CLiteralTree; +import com.ibm.engine.language.c.tree.CTree; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nonnull; + +/** + * Language translation implementation for C/C++. + * + *

Extracts function names, object type strings, and parameter information from the CTree + * hierarchy. Since C/C++ parsing provides only syntactic information (no type inference), all + * parameter types match any expected type. + */ +public final class CLanguageTranslation implements ILanguageTranslation { + + @Nonnull + @Override + public Optional getMethodName( + @Nonnull MatchContext matchContext, @Nonnull CTree methodInvocation) { + if (methodInvocation instanceof CFunctionCallTree call) { + return Optional.of(call.getFunctionName()); + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional getInvokedObjectTypeString( + @Nonnull MatchContext matchContext, @Nonnull CTree methodInvocation) { + if (methodInvocation instanceof CFunctionCallTree call) { + String typeName = call.getObjectTypeName(); + return Optional.of(expectedType -> expectedType.equals(typeName)); + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional getMethodReturnTypeString( + @Nonnull MatchContext matchContext, @Nonnull CTree methodInvocation) { + return Optional.empty(); + } + + @Nonnull + @Override + public List getMethodParameterTypes( + @Nonnull MatchContext matchContext, @Nonnull CTree methodInvocation) { + List args = null; + if (methodInvocation instanceof CFunctionCallTree call) { + args = call.getArguments(); + } + if (args == null || args.isEmpty()) { + return Collections.emptyList(); + } + // No semantic type info; every argument matches any expected type + List types = new ArrayList<>(args.size()); + for (int i = 0; i < args.size(); i++) { + types.add(expectedType -> true); + } + return types; + } + + @Nonnull + @Override + public Optional resolveIdentifierAsString( + @Nonnull MatchContext matchContext, @Nonnull CTree identifierTree) { + if (identifierTree instanceof CLiteralTree literal) { + return Optional.of(literal.getValue()); + } else if (identifierTree instanceof CIdentifierTree identifier) { + return Optional.of(identifier.getName()); + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional getEnumIdentifierName( + @Nonnull MatchContext matchContext, @Nonnull CTree enumIdentifier) { + if (enumIdentifier instanceof CIdentifierTree identifier) { + return Optional.of(identifier.getName()); + } else if (enumIdentifier instanceof CLiteralTree literal) { + return Optional.of(literal.getValue()); + } + return Optional.empty(); + } + + @Nonnull + @Override + public Optional getEnumClassName( + @Nonnull MatchContext matchContext, @Nonnull CTree enumClass) { + // C does not have enum classes in the OOP sense + return Optional.empty(); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/CScanContext.java b/engine/src/main/java/com/ibm/engine/language/c/CScanContext.java new file mode 100644 index 000000000..ff179e9f9 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/CScanContext.java @@ -0,0 +1,79 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c; + +import com.ibm.engine.language.IScanContext; +import com.ibm.engine.language.c.tree.CTree; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.issue.NewIssue; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.rule.RuleKey; +import org.sonar.check.Rule; + +/** + * C/C++ scan context wrapping the SonarQube SensorContext. + * + *

Since there is no sonar-c framework, this record holds the raw {@link SensorContext} and + * {@link InputFile} and reports issues directly via the sensor API. + * + * @param sensorContext the SonarQube sensor context for the current analysis + * @param inputFile the file currently being analysed + * @param repositoryKey the rule repository key (e.g. {@code "sonar-c-crypto"}) + */ +public record CScanContext( + @Nonnull SensorContext sensorContext, + @Nonnull InputFile inputFile, + @Nonnull String repositoryKey) + implements IScanContext { + + @Override + public void reportIssue( + @Nonnull CCheck currentRule, @Nonnull CTree tree, @Nonnull String message) { + String ruleKey = getRuleKey(currentRule); + if (ruleKey == null) { + return; + } + int line = Math.max(1, tree.getLine()); + NewIssue issue = sensorContext.newIssue(); + NewIssueLocation location = + issue.newLocation().on(inputFile).at(inputFile.selectLine(line)).message(message); + issue.forRule(RuleKey.of(repositoryKey, ruleKey)).at(location).save(); + } + + @Nullable private static String getRuleKey(@Nonnull CCheck rule) { + Rule annotation = rule.getClass().getAnnotation(Rule.class); + return annotation != null ? annotation.key() : null; + } + + @Nonnull + @Override + public InputFile getInputFile() { + return inputFile; + } + + @Nonnull + @Override + public String getFilePath() { + return inputFile.uri().getPath(); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/CSymbol.java b/engine/src/main/java/com/ibm/engine/language/c/CSymbol.java new file mode 100644 index 000000000..faf8e039e --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/CSymbol.java @@ -0,0 +1,47 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c; + +import javax.annotation.Nonnull; + +/** + * Minimal symbol representation for C/C++ detection. + * + *

Since C/C++ parsing does not provide semantic symbol resolution, this class holds only the + * identifier name. + */ +public final class CSymbol { + + @Nonnull private final String name; + + public CSymbol(@Nonnull String name) { + this.name = name; + } + + @Nonnull + public String getName() { + return name; + } + + @Override + public String toString() { + return "CSymbol{" + name + "}"; + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/tree/CBlockTree.java b/engine/src/main/java/com/ibm/engine/language/c/tree/CBlockTree.java new file mode 100644 index 000000000..3f14796ba --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/tree/CBlockTree.java @@ -0,0 +1,65 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c.tree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; + +/** + * Represents a block of statements in C/C++ source code (typically a function body). + * + *

This is the entry point for the detection engine — the sensor dispatches per-function blocks to + * the engine for crypto pattern detection. + */ +public final class CBlockTree implements CTree { + + private final int line; + private final int column; + @Nonnull private final List statements; + + public CBlockTree(int line, int column, @Nonnull List statements) { + this.line = line; + this.column = column; + this.statements = new ArrayList<>(statements); + } + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return column; + } + + @Nonnull + @Override + public String getText() { + return ""; + } + + @Nonnull + public List getStatements() { + return Collections.unmodifiableList(statements); + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/tree/CFunctionCallTree.java b/engine/src/main/java/com/ibm/engine/language/c/tree/CFunctionCallTree.java new file mode 100644 index 000000000..032319fe5 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/tree/CFunctionCallTree.java @@ -0,0 +1,113 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c.tree; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Represents a C/C++ function call expression such as {@code EVP_EncryptInit_ex(ctx, + * EVP_aes_128_cbc(), ...)}. + * + *

The {@code objectTypeName} maps the function to a library namespace (e.g. {@code "EVP"} for + * {@code EVP_EncryptInit_ex}), allowing the engine's type-matching infrastructure to work with C's + * flat function namespace. + */ +public final class CFunctionCallTree implements CTree { + + private final int line; + private final int column; + + /** The function name (e.g. "EVP_EncryptInit_ex"). */ + @Nonnull private final String functionName; + + /** The library/namespace prefix (e.g. "EVP"). */ + @Nonnull private final String objectTypeName; + + /** The argument trees in call order. */ + @Nonnull private final List arguments; + + /** Optional identifier this call result is assigned to (for depending rule tracking). */ + @Nullable private final String assignedIdentifier; + + /** The enclosing block tree (for depending rule context). */ + @Nullable private final CBlockTree enclosingBlock; + + public CFunctionCallTree( + int line, + int column, + @Nonnull String functionName, + @Nonnull String objectTypeName, + @Nonnull List arguments, + @Nullable String assignedIdentifier, + @Nullable CBlockTree enclosingBlock) { + this.line = line; + this.column = column; + this.functionName = functionName; + this.objectTypeName = objectTypeName; + this.arguments = new ArrayList<>(arguments); + this.assignedIdentifier = assignedIdentifier; + this.enclosingBlock = enclosingBlock; + } + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return column; + } + + @Nonnull + @Override + public String getText() { + return objectTypeName + "::" + functionName + "(" + arguments.size() + " args)"; + } + + @Nonnull + public String getFunctionName() { + return functionName; + } + + @Nonnull + public String getObjectTypeName() { + return objectTypeName; + } + + @Nonnull + public List getArguments() { + return Collections.unmodifiableList(arguments); + } + + @Nullable + public String getAssignedIdentifier() { + return assignedIdentifier; + } + + @Nullable + public CBlockTree getEnclosingBlock() { + return enclosingBlock; + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/tree/CIdentifierTree.java b/engine/src/main/java/com/ibm/engine/language/c/tree/CIdentifierTree.java new file mode 100644 index 000000000..c819e2cb7 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/tree/CIdentifierTree.java @@ -0,0 +1,60 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c.tree; + +import javax.annotation.Nonnull; + +/** + * Represents a C/C++ identifier (variable name, macro name, etc.) used as an argument or in + * expressions where the resolved value is not immediately known. + */ +public final class CIdentifierTree implements CTree { + + private final int line; + private final int column; + @Nonnull private final String name; + + public CIdentifierTree(int line, int column, @Nonnull String name) { + this.line = line; + this.column = column; + this.name = name; + } + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return column; + } + + @Nonnull + @Override + public String getText() { + return name; + } + + @Nonnull + public String getName() { + return name; + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/tree/CLiteralTree.java b/engine/src/main/java/com/ibm/engine/language/c/tree/CLiteralTree.java new file mode 100644 index 000000000..1cb04df09 --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/tree/CLiteralTree.java @@ -0,0 +1,64 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c.tree; + +import javax.annotation.Nonnull; + +/** + * Represents a literal value in C/C++ source code (string or numeric constant). + * + *

The {@code value} field contains the raw literal text. For string literals, surrounding quotes + * are stripped. + */ +public final class CLiteralTree implements CTree { + + private final int line; + private final int column; + + /** The literal value. For strings: content without quotes. For integers: the numeric string. */ + @Nonnull private final String value; + + public CLiteralTree(int line, int column, @Nonnull String value) { + this.line = line; + this.column = column; + this.value = value; + } + + @Override + public int getLine() { + return line; + } + + @Override + public int getColumn() { + return column; + } + + @Nonnull + @Override + public String getText() { + return value; + } + + @Nonnull + public String getValue() { + return value; + } +} diff --git a/engine/src/main/java/com/ibm/engine/language/c/tree/CTree.java b/engine/src/main/java/com/ibm/engine/language/c/tree/CTree.java new file mode 100644 index 000000000..f9de45a1c --- /dev/null +++ b/engine/src/main/java/com/ibm/engine/language/c/tree/CTree.java @@ -0,0 +1,42 @@ +/* + * Sonar Cryptography Plugin + * Copyright (C) 2024 PQCA + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.ibm.engine.language.c.tree; + +import javax.annotation.Nonnull; + +/** + * Base interface for C/C++ AST nodes used in the detection engine. + * + *

Implementations of this interface wrap parser-generated tree nodes and expose only the + * information needed by the detection engine. This keeps the engine free of parser-specific + * dependencies. + */ +public interface CTree { + + /** Returns the 1-based line number of this node in the source file. */ + int getLine(); + + /** Returns the 0-based character offset within the line. */ + int getColumn(); + + /** Returns a brief human-readable description of this tree node for debugging purposes. */ + @Nonnull + String getText(); +}