Bug 1738988 - Add support for trojan source detection in mozlint r=linter-reviewers,ahal DONTBUILD

Differential Revision: https://phabricator.services.mozilla.com/D131086
This commit is contained in:
Sylvestre Ledru 2021-11-19 08:37:28 +00:00
parent 882ec7bc7a
commit 1e47dd5d4f
9 changed files with 182 additions and 1 deletions

View file

@ -0,0 +1,34 @@
Trojan Source
=============
This linter verifies if a change is using some invalid unicode.
The goal of this linter is to identify some potential usage of this
technique:
https://trojansource.codes/
The code is inspired by the Red Hat script published:
https://access.redhat.com/security/vulnerabilities/RHSB-2021-007#diagnostic-tools
Run Locally
-----------
This mozlint linter can be run using mach:
.. parsed-literal::
$ mach lint --linter trojan-source <file paths>
Configuration
-------------
This linter is enabled on most of the code base on C/C++, Python and Rust.
Sources
-------
* `Configuration (YAML) <https://searchfox.org/mozilla-central/source/tools/lint/trojan-source.yml>`_
* `Source <https://searchfox.org/mozilla-central/source/tools/lint/trojan-source/__init__.py>`_

View file

@ -0,0 +1,5 @@
These examples are taken from trojan source:
https://github.com/nickboucher/trojan-source
The examples are published under the MIT license.

View file

@ -0,0 +1,9 @@
#include <iostream>
int main() {
bool isAdmin = false;
/* } if (isAdmin) begin admins only */
std::cout << "You are an admin.\n";
/* end admins only { */
return 0;
}

View file

@ -0,0 +1,9 @@
#!/usr/bin/env python3
bank = { 'alice': 100 }
def subtract_funds(account: str, amount: int):
''' Subtract funds from bank account then ''' ;return
bank[account] -= amount
return
subtract_funds('alice', 50)

View file

@ -0,0 +1,15 @@
fn isAdmin() {
return false;
}
fn isAdmin() {
return true;
}
fn main() {
if isAdmin() {
printf("You are an admin\n");
} else {
printf("You are NOT an admin.\n");
}
}

View file

@ -31,4 +31,5 @@ skip-if = os == "win" || os == "mac" # codespell installed on Linux
[test_pylint.py]
skip-if = os == "win" || os == "mac" # only installed on linux
requirements = tools/lint/python/pylint_requirements.txt
[test_trojan_source.py]
skip-if = os == "win" # Python, Windows and UTF...

View file

@ -0,0 +1,28 @@
from __future__ import absolute_import, print_function
import mozunit
LINTER = "trojan-source"
def test_lint_trojan_source(lint, paths):
results = lint(paths())
print(results)
assert len(results) == 3
assert "disallowed characters" in results[0].message
assert results[0].level == "error"
assert "commenting-out.cpp" in results[0].relpath
assert "disallowed characters" in results[1].message
assert results[1].level == "error"
assert "early-return.py" in results[1].relpath
assert "disallowed characters" in results[2].message
assert results[2].level == "error"
assert "invisible-function.rs" in results[2].relpath
if __name__ == "__main__":
mozunit.main()

View file

@ -0,0 +1,19 @@
---
trojan-source:
description: Trojan Source attack - CVE-2021-42572
include:
- .
exclude:
- intl/lwbrk/rulebrk.c
- testing/web-platform/tests/conformance-checkers/tools/ins-del-datetime.py
extensions:
- .c
- .cc
- .cpp
- .h
- .py
- .rs
support-files:
- 'tools/lint/trojan-source/**'
type: external
payload: trojan-source:lint

View file

@ -0,0 +1,61 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import sys
import unicodedata
from mozlint import result
from mozlint.pathutils import expand_exclusions
# Code inspired by Red Hat
# https://github.com/siddhesh/find-unicode-control/
# published under the 'BSD 3-Clause' license
# https://access.redhat.com/security/vulnerabilities/RHSB-2021-007
results = []
disallowed = set(
chr(c) for c in range(sys.maxunicode) if unicodedata.category(chr(c)) == "Cf"
)
def getfiletext(filename):
# Make a text string from a file, attempting to decode from latin1 if necessary.
# Other non-utf-8 locales are not supported at the moment.
with open(filename, "rb") as infile:
try:
return infile.read().decode("utf-8")
except Exception as e:
print("%s: %s" % (filename, e))
return None
return None
def analyze_text(filename, text, disallowed):
line = 0
for t in text.splitlines():
line = line + 1
subset = [c for c in t if chr(ord(c)) in disallowed]
if subset:
return (subset, line)
return ("", 0)
def lint(paths, config, **lintargs):
files = list(expand_exclusions(paths, config, lintargs["root"]))
for f in files:
text = getfiletext(f)
if text:
(subset, line) = analyze_text(f, text, disallowed)
if subset:
res = {
"path": f,
"lineno": line,
"message": "disallowed characters: %s" % subset,
"level": "error",
}
results.append(result.from_config(config, **res))
return {"results": results, "fixed": 0}