diff --git a/docs/diagnostics/DisableSafeModeForExternalDataProcessors.md b/docs/diagnostics/DisableSafeModeForExternalDataProcessors.md new file mode 100644 index 00000000000..b74f030246f --- /dev/null +++ b/docs/diagnostics/DisableSafeModeForExternalDataProcessors.md @@ -0,0 +1,57 @@ +# Отключение безопасного режима во внешних обработках, отчетах для БСП (DisableSafeModeForExternalDataProcessors) + + + +## Описание диагностики + + +Помимо программного кода конфигурации, в прикладном решении может исполняться сторонний программный код, который может +быть подключен с помощью внешних отчетов, внешних обработок, расширений конфигурации, внешних компонент или другими +способами, например, с помощью стороннего (по отношению к конфигурации) программного кода, надежность которого +разработчик гарантировать не может (далее – внешний код). При этом злоумышленник может предусмотреть в нем различные +деструктивные действия (как в самом внешнем коде, так и опосредовано, через запуск внешних приложений, внешних +компонент, COM-объектов), которые могут нанести вред компьютерам пользователей, серверным компьютерам, а также данным в +программе. + +Перечисленные проблемы безопасности особенно критичны при работе конфигураций в модели сервиса. Например, получив доступ +к сервису, вредоносный код может получить доступ сразу ко всем приложениям всех пользователей сервиса. + +Поэтому важно контролировать выполнение подобного внешнего кода в безопасном режиме, в исключительных случаях точечно +разрешая выполнять код в небезопасном режиме после верификации кода. + +Правило проверяет отключение безопасного режима во внешних отчетах и обработках, подключаемых к подсистеме " +Дополнительные отчеты и обработки" для БСП-совместимых конфигураций. + +## Примеры + + + +```bsl +Функция СведенияОВнешнейОбработке() Экспорт + + ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1"); + ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительнаяОбработка(); + + ПараметрыРегистрации.БезопасныйРежим = Ложь; // будет выдано замечание + ПараметрыРегистрации.БезопасныйРежим = Истина; // замечания не будет + ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь); // будет выдано замечание + ПараметрыРегистрации.Вставить("БезопасныйРежим", Истина); // замечания не будет + + Возврат ПараметрыРегистрации; +КонецФункции +``` + +## Источники + + + + +- [Статья "Безопасный режим работы" - руководство разработчика 1С 8.3.22](https://its.1c.ru/db/v8322doc#bookmark:dev:TI000000186@ee788d9) +- [Стандарт "Ограничение на выполнение «внешнего» кода"](https://its.1c.ru/db/v8std/content/669/hdoc) +- [Стандарт "Безопасность прикладного программного интерфейса сервера"](https://its.1c.ru/db/v8std/content/678/hdoc) +- [Стандарт "Ограничения на использование Выполнить и Вычислить на сервере"](https://its.1c.ru/db/v8std#content:770:hdoc) +- [Стандарт Использование привилегированного режима](https://its.1c.ru/db/v8std/content/485/hdoc) diff --git a/docs/en/diagnostics/DisableSafeModeForExternalDataProcessors.md b/docs/en/diagnostics/DisableSafeModeForExternalDataProcessors.md new file mode 100644 index 00000000000..7bcd049c5eb --- /dev/null +++ b/docs/en/diagnostics/DisableSafeModeForExternalDataProcessors.md @@ -0,0 +1,20 @@ +# Disable safe mode for external data processors, reports for SSL (DisableSafeModeForExternalDataProcessors) + + + +## Description + + + +## Examples + + + +## Sources + + + diff --git a/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic.java b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic.java new file mode 100644 index 00000000000..8c617c08087 --- /dev/null +++ b/src/main/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic.java @@ -0,0 +1,222 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2023 + * Alexey Sosnoviy , Nikita Fedkin and contributors + * + * SPDX-License-Identifier: LGPL-3.0-or-later + * + * BSL Language Server is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * BSL Language Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with BSL Language Server. + */ +package com.github._1c_syntax.bsl.languageserver.diagnostics; + +import com.github._1c_syntax.bsl.languageserver.context.symbol.MethodSymbol; +import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata; +import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticScope; +import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity; +import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag; +import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType; +import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex; +import com.github._1c_syntax.bsl.languageserver.utils.Ranges; +import com.github._1c_syntax.bsl.parser.BSLParser; +import com.github._1c_syntax.bsl.parser.BSLParserRuleContext; +import com.github._1c_syntax.utils.CaseInsensitivePattern; +import org.antlr.v4.runtime.tree.ParseTree; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +@DiagnosticMetadata( + type = DiagnosticType.VULNERABILITY, + severity = DiagnosticSeverity.MAJOR, + minutesToFix = 5, + tags = { + DiagnosticTag.SUSPICIOUS + }, + scope = DiagnosticScope.BSL, + activatedByDefault = true +) + +public class DisableSafeModeForExternalDataProcessorsDiagnostic extends AbstractVisitorDiagnostic { + + public static final String MAIN_METHOD_NAME = "СведенияОВнешнейОбработке"; + private static final String SAFE_MODE_PARAMETER = "БЕЗОПАСНЫЙРЕЖИМ"; + private static final String SAFE_MODE_PARAMETER_WITH_QUOTES = "\"" + SAFE_MODE_PARAMETER + "\""; + private static final Pattern INSERT_METHOD_PATTERN = CaseInsensitivePattern.compile("вставить|insert"); + private final ReferenceIndex referenceIndex; + private final Map> nodes = new HashMap<>(); + private MethodSymbol methodSymbol; + private Optional mainCollection = Optional.empty(); + + public DisableSafeModeForExternalDataProcessorsDiagnostic(ReferenceIndex referenceIndex) { + this.referenceIndex = referenceIndex; + } + + private static boolean isFalseExpression(Optional expressionContext) { + return expressionContext + .map(BSLParser.ExpressionContext::member) + .filter(memberContexts -> !memberContexts.isEmpty()) + .map(memberContexts -> memberContexts.get(0)) + .map(BSLParser.MemberContext::constValue) + .map(BSLParser.ConstValueContext::FALSE) + .isPresent(); + } + + private static boolean haveSafeModeText(BSLParser.CallParamContext callParamContext) { + return Optional.of(callParamContext) + .map(BSLParser.CallParamContext::expression) + .map(BSLParser.ExpressionContext::member) + .filter(memberContexts -> !memberContexts.isEmpty()) + .map(memberContexts -> memberContexts.get(0)) + .map(BSLParser.MemberContext::constValue) + .map(BSLParser.ConstValueContext::string) + .map(BSLParserRuleContext::getText) + .filter(name -> name.equalsIgnoreCase(SAFE_MODE_PARAMETER_WITH_QUOTES)) + .isPresent(); + } + + @Override + public ParseTree visitFile(BSLParser.FileContext ctx) { + processFile(ctx); + return null; + + } + + public void processFile(BSLParser.FileContext ctx) { + final var mainMethodSymbol = getMainMethodSymbol(); + if (mainMethodSymbol + .isEmpty()) { + return; + } + methodSymbol = mainMethodSymbol.get(); + + final var references = referenceIndex.getReferencesFrom(methodSymbol); + if (references.isEmpty()) { + return; + } + + super.visitFile(ctx); // вычисляю имя возвращаемой переменной в mainCollectionName + if (mainCollection.isPresent() && !nodes.isEmpty()) { + nodes.get(mainCollection.get()) + .forEach(diagnosticStorage::addDiagnostic); + } + } + + @NotNull + private Optional getMainMethodSymbol() { + return documentContext.getSymbolTree().getMethodSymbol(MAIN_METHOD_NAME) + .filter(MethodSymbol::isFunction) + .filter(MethodSymbol::isExport); + } + + @Override + public ParseTree visitFunction(BSLParser.FunctionContext ctx) { + // TODO проверить на вхождение range плюс или минус 1 + if (Ranges.containsRange(methodSymbol.getRange(), Ranges.create(ctx)) && + Optional.ofNullable(ctx.funcDeclaration()) + .map(BSLParser.FuncDeclarationContext::subName) + .map(BSLParser.SubNameContext::IDENTIFIER) + .map(ParseTree::getText) + .filter(s -> s.equalsIgnoreCase(MAIN_METHOD_NAME)) + .isPresent()) { + return super.visitFunction(ctx); + } + return null; // нет смысла анализировать другие методы + } + + @Override + public ParseTree visitProcedure(BSLParser.ProcedureContext ctx) { + return null; // нет смысла анализировать другие методы + } + + @Override + public ParseTree visitReturnStatement(BSLParser.ReturnStatementContext ctx) { + // TODO проверить на вхождение range плюс или минус 1 + if (mainCollection.isEmpty() && Ranges.containsRange(methodSymbol.getRange(), Ranges.create(ctx))) { + mainCollection = Optional.ofNullable(ctx.expression()) + .map(BSLParser.ExpressionContext::member) + .filter(memberContexts -> !memberContexts.isEmpty()) + .map(memberContexts -> memberContexts.get(0)) + .map(BSLParser.MemberContext::complexIdentifier) + .map(BSLParser.ComplexIdentifierContext::IDENTIFIER) + .map(ParseTree::getText); + } + return null; //нет смысла идти глубже + } + + @Override + public ParseTree visitAssignment(BSLParser.AssignmentContext ctx) { + processAssignment(ctx); + return null; // спускаться глубже нет смысла + } + + public void processAssignment(BSLParser.AssignmentContext ctx) { + final var lValueContext = Optional.of(ctx.lValue()); + final var varName = lValueContext.map(BSLParser.LValueContext::IDENTIFIER) + .map(ParseTree::getText); + if (varName.isEmpty()) { + return; + } + final var terminalNodeSafeMode = lValueContext + .map(BSLParser.LValueContext::acceptor) + .map(BSLParser.AcceptorContext::accessProperty) + .map(BSLParser.AccessPropertyContext::IDENTIFIER) + .filter(terminalNode1 -> terminalNode1.getText().equalsIgnoreCase(SAFE_MODE_PARAMETER)); + + if (terminalNodeSafeMode.isPresent() && isFalseExpression(Optional.ofNullable(ctx.expression()))) { + nodes.computeIfAbsent(varName.get(), name -> new ArrayList<>()).add(ctx); + } + } + + @Override + public ParseTree visitCallStatement(BSLParser.CallStatementContext ctx) { + processCallStatement(ctx); + return null; + } + + public void processCallStatement(BSLParser.CallStatementContext ctx) { + final var varName = Optional.ofNullable(ctx.IDENTIFIER()) + .map(ParseTree::getText); + if (varName.isEmpty()) { + return; + } + + final var methodCallContext = Optional.of(ctx) + .map(BSLParser.CallStatementContext::accessCall) + .map(BSLParser.AccessCallContext::methodCall); + final var methodNameContext = methodCallContext + .map(BSLParser.MethodCallContext::methodName) + .filter(methodName -> INSERT_METHOD_PATTERN.matcher(methodName.getText()).matches()); + if (methodNameContext.isEmpty()) { + return; + } + final var expressionContext = methodCallContext + .map(BSLParser.MethodCallContext::doCall) + .map(BSLParser.DoCallContext::callParamList) + .map(BSLParser.CallParamListContext::callParam) + .filter(callParamContexts -> callParamContexts.size() == 2) + .filter(callParamContexts -> haveSafeModeText(callParamContexts.get(0))) + .map(callParamContexts -> callParamContexts.get(1)) + .map(BSLParser.CallParamContext::expression); + + if (isFalseExpression(expressionContext)) { + nodes.computeIfAbsent(varName.get(), name -> new ArrayList<>()).add(ctx); + } + } +} diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json index 9310767b6eb..1b89434ce81 100644 --- a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/configuration/parameters-schema.json @@ -500,6 +500,16 @@ "title": "Deprecated ManagedForm type", "$id": "#/definitions/DeprecatedTypeManagedForm" }, + "DisableSafeModeForExternalDataProcessors": { + "description": "Disable safe mode for external data processors, reports for SSL", + "default": true, + "type": [ + "boolean", + "object" + ], + "title": "Disable safe mode for external data processors, reports for SSL", + "$id": "#/definitions/DisableSafeModeForExternalDataProcessors" + }, "DuplicateRegion": { "description": "Duplicate regions", "default": true, diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic_en.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic_en.properties new file mode 100644 index 00000000000..9ff22b23048 --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic_en.properties @@ -0,0 +1,2 @@ +diagnosticMessage=Check the safe mode setting +diagnosticName=Disable safe mode for external data processors, reports for SSL diff --git a/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic_ru.properties b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic_ru.properties new file mode 100644 index 00000000000..5ef4bf9a69a --- /dev/null +++ b/src/main/resources/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic_ru.properties @@ -0,0 +1,2 @@ +diagnosticMessage=Проверьте отключение безопасного режима +diagnosticName=Отключение безопасного режима во внешних обработках, отчетах для БСП diff --git a/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnosticTest.java b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnosticTest.java new file mode 100644 index 00000000000..9d63699f78a --- /dev/null +++ b/src/test/java/com/github/_1c_syntax/bsl/languageserver/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnosticTest.java @@ -0,0 +1,46 @@ +/* + * This file is a part of BSL Language Server. + * + * Copyright (c) 2018-2023 + * Alexey Sosnoviy , Nikita Fedkin and contributors + * + * SPDX-License-Identifier: LGPL-3.0-or-later + * + * BSL Language Server is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3.0 of the License, or (at your option) any later version. + * + * BSL Language Server is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with BSL Language Server. + */ +package com.github._1c_syntax.bsl.languageserver.diagnostics; + +import org.eclipse.lsp4j.Diagnostic; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat; + +class DisableSafeModeForExternalDataProcessorsDiagnosticTest extends AbstractDiagnosticTest { + DisableSafeModeForExternalDataProcessorsDiagnosticTest() { + super(DisableSafeModeForExternalDataProcessorsDiagnostic.class); + } + + @Test + void test() { + + List diagnostics = getDiagnostics(); + + assertThat(diagnostics, true) + .hasRange(5, 4, 47) + .hasRange(7, 4, 58) + .hasSize(2); + } +} diff --git a/src/test/resources/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic.bsl b/src/test/resources/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic.bsl new file mode 100644 index 00000000000..c2881582460 --- /dev/null +++ b/src/test/resources/diagnostics/DisableSafeModeForExternalDataProcessorsDiagnostic.bsl @@ -0,0 +1,25 @@ +Функция СведенияОВнешнейОбработке() Экспорт + + ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1"); + ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительнаяОбработка(); + + ПараметрыРегистрации.БезопасныйРежим = Ложь; // ошибка + ПараметрыРегистрации.БезопасныйРежим = Истина; + ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь); // ошибка + ПараметрыРегистрации.Вставить("БезопасныйРежим", Истина); + + Возврат ПараметрыРегистрации; +КонецФункции + +Функция ДругаяФункция() Экспорт + + ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1"); + ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительнаяОбработка(); + + ПараметрыРегистрации.БезопасныйРежим = Ложь; + ПараметрыРегистрации.БезопасныйРежим = Истина; + ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь); + ПараметрыРегистрации.Вставить("БезопасныйРежим", Истина); + + Возврат ПараметрыРегистрации; +КонецФункции