app("APACHE LOG4J - CVE-2021-45105 - v1.3, b1"):
    requires(version: "ARMR/1.3")

    /***************************************************************************
        CVE-2021-45105 (APACHE LOG4J)

        CVSS 3.0 Summary:
            Base Score              | 7.5 HIGH
            Attack Vector           | Network
            Attack Complexity       | Low
            Privileges Required     | None
            User Interaction        | None
            Scope                   | Unchanged
            Confidentiality Impact  | None
            Integrity Impact        | None
            Availability Impact     | High

        Description:
            Apache Log4j2 versions 2.0-alpha1 through 2.16.0 (excluding 2.12.3) did not
            protect from uncontrolled recursion from self-referential lookups. This allows
            an attacker with control over Thread Context Map data to cause a denial of
            service when a crafted string is interpreted. This issue was fixed in
            Log4j 2.17.0 and 2.12.3.

        Resources:
            https://nvd.nist.gov/vuln/detail/CVE-2021-45105
            https://issues.apache.org/jira/browse/LOG4J2-3230
            https://github.com/apache/logging-log4j2/commit/806023265f8c905b2dd1d81fd2458f64b2ea0b5e

        Affected Operating System:
            Any

        Affected Versions:
            log4j2: 2.0 - 2.16.0

        Fixed Versions:
            log4j2: 2.17.0, 2.12.3

        Tested Versions:
             log4j2: 2.0 - 2.16.0

        Protection Provided:
            Functional

        Known Issues:
            Recursive substitution of patterns is disabled

        Patch Version:
            See mod name

    ***************************************************************************/

    patch("CVE-2021-45105 :0"):
        function("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I",
            checksums: ["e26f4a4a5f",   // 2.0 - 2.5
                        "830d99269e",   // 2.6 - 2.6.2
                        "4ffeec1fda",   // 2.7
                        "9cb6ad89aa",   // 2.8 - 2.13.0
                        "69059844d3"])  // 2.13.1 - 2.16.0
        call("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I")
        code(language: "java"):
            private static JavaMethod substituteMethod;

            public void load() {
                substituteMethod = JavaMethod.load("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I");
            }

            public void patch(JavaFrame frame) {
                Object thisRef = frame.loadThisOperand();
                int change = 0;
                if (thisRef != null && !thisRef.getClass().toString().endsWith("$RuntimeStrSubstitutor")) {
                    Object event = frame.loadObjectOperand(1);
                    Object bufName = frame.loadObjectOperand(2);
                    int offset = frame.loadIntOperand(3);
                    int bufNameLength = frame.loadIntOperand(4);
                    Object priorVariables = frame.loadObjectOperand(5);
                    change = ((Integer) substituteMethod.call(thisRef, new Object[] {event, bufName, offset, bufNameLength, priorVariables})).intValue();
                } else {
                    log("recursion in message substitution blocked due to CVE-2021-45105", restoreValue("PAYLOAD"));
                }
                frame.storeObjectOperand(0, change);
            }

            private static void log(String msg, Object payload) {
                ArmrEvent event = ArmrEvent.load("ALERT", "LOW");
                event.addExtension("msg", msg);
                if (payload != null) {
                    event.addExtension("payload", payload.toString());
                }
                event.commit();
            }
        endcode
    endpatch

    patch("CVE-2021-45105 :1"):
        function("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I",
            checksums: ["e26f4a4a5f",   // 2.0 - 2.5
                        "830d99269e",   // 2.6 - 2.6.2
                        "4ffeec1fda",   // 2.7
                        "9cb6ad89aa",   // 2.8 - 2.13.0
                        "69059844d3"])  // 2.13.1 - 2.16.0
        call("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;II)Z")
        code(language: "java", import: ["java.util.ArrayList"]):
            private static JavaMethod substituteMethod;

            public void load() {
                substituteMethod = JavaMethod.load("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I");
            }

            public void patch(JavaFrame frame) {
                Object thisRef = frame.loadThisOperand();
                Object event = frame.loadObjectOperand(1);
                Object bufName = frame.loadObjectOperand(2);
                int offset = frame.loadIntOperand(3);
                int bufNameLength = frame.loadIntOperand(4);

                Object priorVariables = frame.loadObjectVariable(5);
                if (priorVariables == null) {
                    priorVariables = new ArrayList();
                    frame.storeObjectVariable(5, priorVariables);
                }
                substituteMethod.call(thisRef, new Object[] {event, bufName, offset, bufNameLength, priorVariables});
            }
        endcode
    endpatch

    patch("CVE-2021-45105 :2"):
        function("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I",
            checksums: ["e26f4a4a5f",   // 2.0 - 2.5
                        "830d99269e",   // 2.6 - 2.6.2
                        "4ffeec1fda",   // 2.7
                        "9cb6ad89aa",   // 2.8 - 2.13.0
                        "69059844d3"])  // 2.13.1 - 2.16.0
        call("org/apache/logging/log4j/core/lookup/StrSubstitutor.checkCyclicSubstitution(Ljava/lang/String;Ljava/util/List;)V")
        code(language: "java"):
            public void patch(JavaFrame frame) {
                  // remove method call
            }
        endcode
    endpatch

    patch("CVE-2021-45105 :3"):
        function("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I",
            checksums: ["e26f4a4a5f",   // 2.0 - 2.5
                        "830d99269e",   // 2.6 - 2.6.2
                        "4ffeec1fda",   // 2.7
                        "9cb6ad89aa",   // 2.8 - 2.13.0
                        "69059844d3"])  // 2.13.1 - 2.16.0
        call("java/util/List.add(Ljava/lang/Object;)Z", occurrences: [2])
        code(language: "java"):
            public void patch(JavaFrame frame) {
                  // remove method call
            }
        endcode
    endpatch

    patch("CVE-2021-45105 :4"):
        function("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I",
            checksums: ["e26f4a4a5f",   // 2.0 - 2.5
                        "830d99269e",   // 2.6 - 2.6.2
                        "4ffeec1fda",   // 2.7
                        "9cb6ad89aa",   // 2.8 - 2.13.0
                        "69059844d3"])  // 2.13.1 - 2.16.0
        call("org/apache/logging/log4j/core/lookup/StrSubstitutor.resolveVariable(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/String;Ljava/lang/StringBuilder;II)Ljava/lang/String;")
        code(language: "java", import: ["java.util.List",
                                      "java.util.LinkedList",
                                      "org.apache.logging.log4j.status.StatusLogger"]):
            private static JavaMethod resolveVariableMethod;
            private static JavaMethod appendWithSeparatorsMethod;

            public void load() {
                resolveVariableMethod = JavaMethod.load("org/apache/logging/log4j/core/lookup/StrSubstitutor.resolveVariable(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/String;Ljava/lang/StringBuilder;II)Ljava/lang/String;");
                appendWithSeparatorsMethod = JavaMethod.load("org/apache/logging/log4j/core/lookup/StrSubstitutor.appendWithSeparators(Ljava/lang/StringBuilder;Ljava/lang/Iterable;Ljava/lang/String;)V");
            }

            public void patch(JavaFrame frame) {
                Object thisRef = frame.loadThisVariable();
                String varName = (String) frame.loadObjectOperand(2);
                List<String> priorVariables = (List<String>) frame.loadObjectVariable(5);
                Object result = null;
                LinkedList<Boolean> isCyclicStack = getIsCyclicStack();
                isCyclicStack.addFirst(isCyclicSubstitution(thisRef, varName, priorVariables));
                if (!isCyclicStack.getFirst()) {
                    Object event = frame.loadObjectOperand(1);
                    Object buf = frame.loadObjectOperand(3);
                    int startPos = frame.loadIntOperand(4);
                    int endPos = frame.loadIntOperand(5);
                    result = resolveVariableMethod.call(thisRef, new Object[] {event, varName, buf, startPos, endPos});
                }
                if (result != null) {
                    saveValue("PAYLOAD", result);
                }
                frame.storeObjectOperand(0, result);
            }

            private LinkedList<Boolean> getIsCyclicStack() {
                LinkedList<Boolean> isCyclicStack = (LinkedList<Boolean>) restoreValue("IS_CYCLIC");
                if (isCyclicStack == null) {
                    isCyclicStack = new LinkedList<Boolean>();
                    saveValue("IS_CYCLIC", isCyclicStack);
                }
                return isCyclicStack;
            }

            private static boolean isCyclicSubstitution(Object thisRef, final String varName, final List<String> priorVariables) {
                if (!priorVariables.contains(varName)) {
                    priorVariables.add(varName);
                    return false;
                }
                final StringBuilder buf = new StringBuilder(256);
                buf.append("Infinite loop in property interpolation of ");
                appendWithSeparatorsMethod.call(thisRef, new Object[] {buf, priorVariables, "->"});
                StatusLogger.getLogger().warn(buf);
                return true;
            }
        endcode
    endpatch

    patch("CVE-2021-45105 :5"):
        function("org/apache/logging/log4j/core/lookup/StrSubstitutor.substitute(Lorg/apache/logging/log4j/core/LogEvent;Ljava/lang/StringBuilder;IILjava/util/List;)I",
            checksums: ["e26f4a4a5f",   // 2.0 - 2.5
                        "830d99269e",   // 2.6 - 2.6.2
                        "4ffeec1fda",   // 2.7
                        "9cb6ad89aa",   // 2.8 - 2.13.0
                        "69059844d3"])  // 2.13.1 - 2.16.0
        call("java/util/List.remove(I)Ljava/lang/Object;")
        code(language: "java", import: ["java.util.List",
                                      "java.util.LinkedList",
                                      "org.apache.logging.log4j.status.StatusLogger"]):
            private static JavaMethod appendWithSeparatorsMethod;

            public void load() {
                appendWithSeparatorsMethod = JavaMethod.load("org/apache/logging/log4j/core/lookup/StrSubstitutor.appendWithSeparators(Ljava/lang/StringBuilder;Ljava/lang/Iterable;Ljava/lang/String;)V");
            }

            public void patch(JavaFrame frame) {
                Object thisRef = frame.loadThisVariable();
                String varName = (String) frame.loadObjectVariable(23);
                List<String> priorVariables = (List<String>) frame.loadObjectVariable(5);
                LinkedList<Boolean> isCyclicStack = (LinkedList<Boolean>) restoreValue("IS_CYCLIC");
                if (!isCyclicStack.removeFirst()) {
                    priorVariables.remove(priorVariables.size() - 1);
                }
            }
        endcode
    endpatch

    patch("CVE-2021-45105 :6"):
        function("org/apache/logging/log4j/core/config/AbstractConfiguration.<init>(Lorg/apache/logging/log4j/core/LoggerContext;Lorg/apache/logging/log4j/core/config/ConfigurationSource;)V",
            checksums: ["ceea6db4b3",     // 2.7
                        "2518f22e19",     // 2.8 - 2.8.2
                        "256798975f",     // 2.9.0 - 2.11.0
                        "c345405ce9",     // 2.11.1
                        "d617ea4e3f",     // 2.11.2
                        "918b5e4b0a",     // 2.12.0 - 2.13.3
                        "4e00040373",     // 2.14.0 - 2.14.1
                        "93e567cf64"])    // 2.15.0 - 2.16.0
        write("org/apache/logging/log4j/core/config/AbstractConfiguration.subst")
        code(language: "java", import: ["org.apache.logging.log4j.core.lookup.StrSubstitutor",
                                      "org.apache.logging.log4j.core.lookup.StrLookup",
                                      "org.apache.logging.log4j.core.LogEvent"]):
            private static JavaField tempLookupField;
            private static JavaField substField;

            public void load() {
               tempLookupField = JavaField.load("org/apache/logging/log4j/core/config/AbstractConfiguration.tempLookup");
               substField = JavaField.load("org/apache/logging/log4j/core/config/AbstractConfiguration.subst");
            }

            public void patch(JavaFrame frame) {
                Object thisRef = frame.loadThisVariable();
                StrLookup tempLookup = (StrLookup) tempLookupField.readObject(thisRef);
                substField.writeObject(thisRef, new RuntimeStrSubstitutor(tempLookup));
            }

            public static class RuntimeStrSubstitutor extends StrSubstitutor {
                RuntimeStrSubstitutor(StrLookup lookup) {
                    super(lookup);
                }
            }
        endcode
    endpatch

    patch("CVE-2021-45105 :7"):
        function("org/apache/logging/log4j/core/config/AbstractConfiguration.<init>(Lorg/apache/logging/log4j/core/config/ConfigurationSource;)V",
            checksums: ["b107cc5cd7",   // 2.0 - 2.0.2
                        "a10f5818e2",   // 2.1 - 2.3
                        "43126248fa",   // 2.4
                        "1d39485949",   // 2.4.1
                        "d92c4931c3",   // 2.5
                        "acce28f199"])  // 2.6 - 2.6.2
        write("org/apache/logging/log4j/core/config/AbstractConfiguration.subst")
        code(language: "java", import: ["org.apache.logging.log4j.core.lookup.StrSubstitutor",
                                      "org.apache.logging.log4j.core.lookup.StrLookup",
                                      "org.apache.logging.log4j.core.LogEvent"]):
            private static JavaField tempLookupField;
            private static JavaField substField;

            public void load() {
               tempLookupField = JavaField.load("org/apache/logging/log4j/core/config/AbstractConfiguration.tempLookup");
               substField = JavaField.load("org/apache/logging/log4j/core/config/AbstractConfiguration.subst");
            }

            public void patch(JavaFrame frame) {
                Object thisRef = frame.loadThisVariable();
                StrLookup tempLookup = (StrLookup) tempLookupField.readObject(thisRef);
                substField.writeObject(thisRef, new RuntimeStrSubstitutor(tempLookup));
            }

            public static class RuntimeStrSubstitutor extends StrSubstitutor {
                RuntimeStrSubstitutor(StrLookup lookup) {
                    super(lookup);
                }
            }
        endcode
    endpatch
endapp
