| // Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'package:analyzer/dart/ast/ast.dart'; |
| import 'package:analyzer/src/diagnostic/diagnostic.dart' as diag; |
| import 'package:analyzer/src/error/listener.dart'; |
| |
| /// A verifier that checks for unsafe Unicode text. |
| /// See: https://nvd.nist.gov/vuln/detail/CVE-2021-22567 |
| class UnicodeTextVerifier { |
| final DiagnosticReporter _diagnosticReporter; |
| UnicodeTextVerifier(this._diagnosticReporter); |
| |
| void verify(CompilationUnit unit, String source) { |
| // Optimization: We assume that we won't find any of these. Doing the loop |
| // in a method that isn't inlined currently reduces the instruction cost per |
| // character from 22 to 12. |
| if (!_hasMatch(source)) return; |
| for (var offset = 0; offset < source.length; ++offset) { |
| var codeUnit = source.codeUnitAt(offset); |
| if (_isCodeUnitMatch(codeUnit)) { |
| var node = unit.nodeCovering(offset: offset); |
| // If it's not in a string literal, we assume we're in a comment. |
| // This can potentially over-report on syntactically incorrect sources |
| // (where Unicode is outside a string or comment). |
| var diagnosticCode = |
| node is SimpleStringLiteral || node is InterpolationString |
| ? diag.textDirectionCodePointInLiteral |
| : diag.textDirectionCodePointInComment; |
| var code = codeUnit.toRadixString(16).toUpperCase(); |
| _diagnosticReporter.report( |
| diagnosticCode |
| .withArguments(codePoint: code) |
| .atOffset(offset: offset, length: 1), |
| ); |
| } |
| } |
| } |
| |
| // Optimization: Specifically never inline this to make sure not to introduce |
| // extra instructions into the right loop. |
| @pragma("vm:never-inline") |
| bool _hasMatch(String source) { |
| for (int offset = 0; offset < source.length; ++offset) { |
| var codeUnit = source.codeUnitAt(offset); |
| if (_isCodeUnitMatch(codeUnit)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Optimization: We want this inlined to avoid the overhead of doing a call, |
| // and we do this to avoid duplicating the code. |
| @pragma("vm:prefer-inline") |
| bool _isCodeUnitMatch(int codeUnit) { |
| // U+202A, U+202B, U+202C, U+202D, U+202E, U+2066, U+2067, U+2068, U+2069. |
| if (0x202a <= codeUnit && |
| codeUnit <= 0x2069 && |
| (codeUnit <= 0x202e || 0x2066 <= codeUnit)) { |
| return true; |
| } |
| return false; |
| } |
| } |