Skip to content

Commit

Permalink
Реализация правила + тесты + precommit
Browse files Browse the repository at this point in the history
  • Loading branch information
artbear committed Nov 20, 2023
1 parent 1c83f52 commit 4b812e4
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 0 deletions.
57 changes: 57 additions & 0 deletions docs/diagnostics/DisableSafeModeForExternalDataProcessors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Отключение безопасного режима во внешних обработках, отчетах для БСП (DisableSafeModeForExternalDataProcessors)

<!-- Блоки выше заполняются автоматически, не трогать -->

## Описание диагностики

<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->
Помимо программного кода конфигурации, в прикладном решении может исполняться сторонний программный код, который может
быть подключен с помощью внешних отчетов, внешних обработок, расширений конфигурации, внешних компонент или другими
способами, например, с помощью стороннего (по отношению к конфигурации) программного кода, надежность которого
разработчик гарантировать не может (далее – внешний код). При этом злоумышленник может предусмотреть в нем различные
деструктивные действия (как в самом внешнем коде, так и опосредовано, через запуск внешних приложений, внешних
компонент, COM-объектов), которые могут нанести вред компьютерам пользователей, серверным компьютерам, а также данным в
программе.

Перечисленные проблемы безопасности особенно критичны при работе конфигураций в модели сервиса. Например, получив доступ
к сервису, вредоносный код может получить доступ сразу ко всем приложениям всех пользователей сервиса.

Поэтому важно контролировать выполнение подобного внешнего кода в безопасном режиме, в исключительных случаях точечно
разрешая выполнять код в небезопасном режиме после верификации кода.

Правило проверяет отключение безопасного режима во внешних отчетах и обработках, подключаемых к подсистеме "
Дополнительные отчеты и обработки" для БСП-совместимых конфигураций.

## Примеры

<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->

```bsl
Функция СведенияОВнешнейОбработке() Экспорт
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительнаяОбработка();
ПараметрыРегистрации.БезопасныйРежим = Ложь; // будет выдано замечание
ПараметрыРегистрации.БезопасныйРежим = Истина; // замечания не будет
ПараметрыРегистрации.Вставить("БезопасныйРежим", Ложь); // будет выдано замечание
ПараметрыРегистрации.Вставить("БезопасныйРежим", Истина); // замечания не будет
Возврат ПараметрыРегистрации;
КонецФункции
```

## Источники

<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников
* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->

- [Статья "Безопасный режим работы" - руководство разработчика 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)
20 changes: 20 additions & 0 deletions docs/en/diagnostics/DisableSafeModeForExternalDataProcessors.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Disable safe mode for external data processors, reports for SSL (DisableSafeModeForExternalDataProcessors)

<!-- Блоки выше заполняются автоматически, не трогать -->

## Description

<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->

## Examples

<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->

## Sources

<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников
* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2023
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> 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<String, List<BSLParserRuleContext>> nodes = new HashMap<>();
private MethodSymbol methodSymbol;
private Optional<String> mainCollection = Optional.empty();

public DisableSafeModeForExternalDataProcessorsDiagnostic(ReferenceIndex referenceIndex) {
this.referenceIndex = referenceIndex;
}

private static boolean isFalseExpression(Optional<BSLParser.ExpressionContext> 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<MethodSymbol> 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
diagnosticMessage=Check the safe mode setting
diagnosticName=Disable safe mode for external data processors, reports for SSL
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
diagnosticMessage=Проверьте отключение безопасного режима
diagnosticName=Отключение безопасного режима во внешних обработках, отчетах для БСП
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2023
* Alexey Sosnoviy <[email protected]>, Nikita Fedkin <[email protected]> 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<DisableSafeModeForExternalDataProcessorsDiagnostic> {
DisableSafeModeForExternalDataProcessorsDiagnosticTest() {
super(DisableSafeModeForExternalDataProcessorsDiagnostic.class);
}

@Test
void test() {

List<Diagnostic> diagnostics = getDiagnostics();

assertThat(diagnostics, true)
.hasRange(5, 4, 47)
.hasRange(7, 4, 58)
.hasSize(2);
}
}
Loading

0 comments on commit 4b812e4

Please sign in to comment.