All files / src/rules no_dynamic_stuff.ts

100% Statements 198/198
98.57% Branches 69/70
100% Functions 12/12
100% Lines 198/198

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 1991x 1x 1x 1x 1x 1x 1x 1x 1x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 22907x 1x 11454x 11454x 11454x 11454x 11454x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 45189x 11454x 11454x 10927x 10927x 11454x 11454x 249x 249x 11454x 11454x 344x 344x 344x 1605x 1605x 49x 49x 49x 1605x 344x 344x 344x 11454x 11454x 1605x 1605x 1605x 1605x 13x 13x 1592x 1605x 4x 4x 1x 1x 1605x 1x 1x 1x 1588x 3x 2x 2x 1587x 2x 2x 2x 1x 1x 1584x 4x 3x 3x 1582x 3x 2x 2x 1578x 6x 2x 2x 1575x 7x 2x 2x 1569x 2x 2x 2x 1562x 1560x 12x 12x 8x 8x 1560x 4x 2x 2x 1548x 1x 1x 1x 1544x 1543x 1543x 1543x 1543x 1543x 1543x 1543x 15x 9x 9x 15x 1556x 1556x 1556x 11454x 11454x 1600x 15x 15x 15x 13x 13x 15x 1587x 1587x 11454x 11454x 3x 3x 3x 11454x 11454x 54x 54x 11454x 11454x 5x 5x 11454x 11454x 7x 7x 11454x 11454x  
import * as Statements from "../abap/2_statements/statements";
import * as Expressions from "../abap/2_statements/expressions";
import {ABAPRule} from "./_abap_rule";
import {BasicRuleConfig} from "./_basic_rule_config";
import {Issue} from "../issue";
import {IRuleMetadata, RuleTag} from "./_irule";
import {ABAPFile} from "../abap/abap_file";
import {ExpressionNode, StatementNode} from "../abap/nodes";
 
export class NoDynamicStuffConf extends BasicRuleConfig {
  /** Detects dynamic method calls, ie. CALL METHOD (name), SET HANDLER and CALL BADI */
  public callMethod: boolean = true;
  /** Detects dynamic CALL FUNCTION, ie. the function module name is not a literal */
  public callFunction: boolean = true;
  /** Detects CALL DATABASE PROCEDURE, the procedure name is always dynamic */
  public callDatabaseProcedure: boolean = true;
  /** Detects dynamic CALL TRANSFORMATION */
  public callTransformation: boolean = true;
  /** Detects dynamic CALL TRANSACTION, ie. the transaction code is not a literal */
  public callTransaction: boolean = true;
  /** Detects dynamic PERFORM */
  public perform: boolean = true;
  /** Detects dynamic SUBMIT */
  public submit: boolean = true;
  /** Detects dynamic CREATE OBJECT */
  public createObject: boolean = true;
  /** Detects dynamic CREATE DATA */
  public createData: boolean = true;
  /** Detects dynamic GET BADI */
  public getBadi: boolean = true;
  /** Detects dynamic ASSIGN, including ASSIGN COMPONENT */
  public assign: boolean = true;
  /** Detects dynamic internal table access, ie. dynamic WHERE, keys, SORT BY and TRANSPORTING */
  public internalTable: boolean = true;
  /** Detects dynamic EXPORT and IMPORT */
  public exportImport: boolean = true;
}
 
export class NoDynamicStuff extends ABAPRule {
 
  private conf = new NoDynamicStuffConf();
 
  public getMetadata(): IRuleMetadata {
    return {
      key: "no_dynamic_stuff",
      title: "No dynamic stuff",
      shortDescription: `Detects dynamic calls and other dynamic language constructs`,
      extendedInformation: `Dynamic constructs cannot be checked statically, they are not found by
where-used lists and refactorings, and they can introduce injection vulnerabilities.
 
Dynamic tokens are also reported when containing a literal, eg. CALL METHOD go_calendar->('RESET_DAY_INFO'),
the syntax check does not resolve these either, so they behave like any other dynamic token.
 
Dynamic SQL is reported by rule dangerous_statement.`,
      tags: [RuleTag.SingleFile, RuleTag.Security],
      badExample: `CALL METHOD (lv_class)=>(lv_method).
CALL METHOD go_calendar->('RESET_DAY_INFO').
CALL FUNCTION lv_function_module.
CREATE OBJECT ref TYPE (lv_class).
ASSIGN (lv_name) TO <fs>.
SORT tab BY (lv_field).`,
      goodExample: `cl_class=>method( ).
go_calendar->reset_day_info( ).
CALL FUNCTION 'ZFOO'.
CREATE OBJECT ref TYPE cl_class.
ASSIGN foo TO <fs>.
SORT tab BY field.`,
    };
  }
 
  public getConfig() {
    return this.conf;
  }
 
  public setConfig(conf: NoDynamicStuffConf): void {
    this.conf = conf;
  }
 
  public runParsed(file: ABAPFile) {
    const issues: Issue[] = [];
 
    for (const statementNode of file.getStatements()) {
      const found = this.check(statementNode);
      if (found !== undefined) {
        issues.push(Issue.atStatement(file, statementNode, "Dynamic " + found,
                                      this.getMetadata().key, this.conf.severity));
      }
    }
 
    return issues;
  }
 
  private check(node: StatementNode): string | undefined {
    const statement = node.get();
 
// note that MethodSource is also used by SET HANDLER and CALL BADI
    if (this.conf.callMethod === true && this.dynamicMethodSource(node) === true) {
      return "method call";
    }
 
    if (statement instanceof Statements.CallFunction) {
      if (this.conf.callFunction === true
          && this.notLiteral(node.findDirectExpression(Expressions.FunctionName))) {
        return "CALL FUNCTION";
      }
    } else if (statement instanceof Statements.CallDatabase) {
      if (this.conf.callDatabaseProcedure === true) {
        return "CALL DATABASE PROCEDURE";
      }
    } else if (statement instanceof Statements.CallTransformation) {
      if (this.conf.callTransformation === true && this.hasDynamic(node)) {
        return "CALL TRANSFORMATION";
      }
    } else if (statement instanceof Statements.CallTransaction) {
// the transaction code is a Source, ie. there is no parenthesized dynamic variant
      if (this.conf.callTransaction === true
          && this.notLiteral(node.findDirectExpression(Expressions.Source))) {
        return "CALL TRANSACTION";
      }
    } else if (statement instanceof Statements.Perform) {
      if (this.conf.perform === true && this.hasDynamic(node)) {
        return "PERFORM";
      }
    } else if (statement instanceof Statements.Submit) {
      if (this.conf.submit === true && this.hasDynamic(node)) {
        return "SUBMIT";
      }
    } else if (statement instanceof Statements.CreateObject) {
      if (this.conf.createObject === true && this.hasDynamic(node)) {
        return "CREATE OBJECT";
      }
    } else if (statement instanceof Statements.CreateData) {
      if (this.conf.createData === true && this.hasDynamic(node)) {
        return "CREATE DATA";
      }
    } else if (statement instanceof Statements.GetBadi) {
      if (this.conf.getBadi === true && this.hasDynamic(node)) {
        return "GET BADI";
      }
    } else if (statement instanceof Statements.Assign
        || statement instanceof Statements.AssignLocalCopy) {
      if (this.conf.assign === true
          && (this.hasDynamic(node) || this.dynamicAssignComponent(node))) {
        return "ASSIGN";
      }
    } else if (statement instanceof Statements.Export) {
      if (this.conf.exportImport === true && this.hasDynamic(node)) {
        return "EXPORT";
      }
    } else if (statement instanceof Statements.Import) {
      if (this.conf.exportImport === true && this.hasDynamic(node)) {
        return "IMPORT";
      }
    } else if (statement instanceof Statements.At
        || statement instanceof Statements.DeleteInternal
        || statement instanceof Statements.InsertInternal
        || statement instanceof Statements.Loop
        || statement instanceof Statements.ModifyInternal
        || statement instanceof Statements.ReadTable
        || statement instanceof Statements.Sort
        || statement instanceof Statements.SortDataset) {
      if (this.conf.internalTable === true && this.hasDynamic(node)) {
        return "internal table access";
      }
    }
 
    return undefined;
  }
 
  private dynamicMethodSource(node: StatementNode): boolean {
    for (const source of node.findAllExpressions(Expressions.MethodSource)) {
// Dynamic is always a direct child of MethodSource, dynamics further down
// are parameters of the method call and not part of the method name
      if (source.findDirectExpression(Expressions.Dynamic) !== undefined) {
        return true;
      }
    }
    return false;
  }
 
  private dynamicAssignComponent(node: StatementNode): boolean {
    const component = node.findDirectExpression(Expressions.AssignSource)?.findExpressionAfterToken("COMPONENT");
    return component !== undefined && this.isLiteral(component) === false;
  }
 
  private hasDynamic(node: StatementNode): boolean {
    return node.findFirstExpression(Expressions.Dynamic) !== undefined;
  }
 
  private notLiteral(node: ExpressionNode | undefined): boolean {
    return node !== undefined && this.isLiteral(node) === false;
  }
 
  private isLiteral(node: ExpressionNode): boolean {
    return node.findDirectExpression(Expressions.Constant) !== undefined;
  }
 
}