Skip to content

Commit

Permalink
Bug 1738988 - Add support for trojan source detection in mozlint r=li…
Browse files Browse the repository at this point in the history
…nter-reviewers,ahal DONTBUILD

Differential Revision: https://phabricator.services.mozilla.com/D131086
  • Loading branch information
sylvestre committed Nov 19, 2021
1 parent 882ec7b commit 1e47dd5
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 1 deletion.
34 changes: 34 additions & 0 deletions docs/code-quality/lint/linters/trojan-source.rst
Original file line number Diff line number Diff line change
@@ -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>`_
5 changes: 5 additions & 0 deletions tools/lint/test/files/trojan-source/README
Original file line number Diff line number Diff line change
@@ -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.

9 changes: 9 additions & 0 deletions tools/lint/test/files/trojan-source/commenting-out.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
9 changes: 9 additions & 0 deletions tools/lint/test/files/trojan-source/early-return.py
Original file line number Diff line number Diff line change
@@ -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)
15 changes: 15 additions & 0 deletions tools/lint/test/files/trojan-source/invisible-function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
fn isAdmin() {
return false;
}

fn is​Admin() {
return true;
}

fn main() {
if is​Admin() {
printf("You are an admin\n");
} else {
printf("You are NOT an admin.\n");
}
}
3 changes: 2 additions & 1 deletion tools/lint/test/python.ini
Original file line number Diff line number Diff line change
Expand Up @@ -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...
28 changes: 28 additions & 0 deletions tools/lint/test/test_trojan_source.py
Original file line number Diff line number Diff line change
@@ -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()
19 changes: 19 additions & 0 deletions tools/lint/trojan-source.yml
Original file line number Diff line number Diff line change
@@ -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
61 changes: 61 additions & 0 deletions tools/lint/trojan-source/__init__.py
Original file line number Diff line number Diff line change
@@ -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}

0 comments on commit 1e47dd5

Please sign in to comment.