Giter Club home page Giter Club logo

libddwaf's Introduction

Build Test fuzz Coverage status License License

Datadog's WAF & RASP Engine

libddwaf is Datadog's implementation of a Web Application Firewall (WAF) engine, with a goal of low performance and memory overhead, and embeddability in a wide variety of language runtimes through a C API.

Versioning semantics

libddwaf follows Semantic Versioning 2.0, with a slight twist.

libddwaf is a heir to libsqreen, the latter which was semantically versioned using 0.y.z:

Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.

To mark the break between libsqreen and libddwaf (which involved a lot of renaming and changes), it was decided to bump the major version, but some time was needed still to stabilise the public API. Therefore libddwaf's 1.y.z is operating following semver's usual 0.y.z, with minor y meaning "breaking change" and patch z meaning "bugfix".

In addition libddwaf's "unstable" marker on releases means the API may evolve and have breaking changes on minor versions. Nonetheless its codebase and resulting binaries are considered production-ready as the "unstable" marker only applies to libddwaf's public API.

Since libddwaf should not be used directly and is wrapped by binding libraries to various languages, any such low-level C API change is handled by Datadog internally and isolated by the higher level binding code, which aims to provide a much stabler high level language-oriented API. In any case, the binding library dependency is directly consumed by the Datadog tracing client libraries, and should there be a breaking change in the binding API it would be handled as gracefully as technically possible within the tracing client library level, and properly handled using the tracing client library dependency verssion constraints so that it picks only compatible versions of the binding library.

Building

Quick Start

This project is built using cmake.

On Linux and Darwin, the following should produce a static and a dynamic library inside of build:

mkdir -p build && cd build
cmake ..
make -j4

A cross-platform way to achieve the same result (e.g on Windows):

cmake -E make_directory build
cd build
cmake ..
cmake --build . --target all -j4

And a more involved example, with specific targets, building, then running the test suite along with debug information:

cmake -E make_directory build packages
cd build
cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX=.. -DCPACK_PACKAGE_DIRECTORY=../packages ..
cmake --build . --config RelWithDebInfo --verbose --target libddwaf_shared --target libddwaf_static  --target waf_test -j
cd ../tests
../build/tests/waf_test

Usage

The general process is as follows:

  • Load processing rules as a ddwaf_object data structure into the WAF engine. This returns a handler.
  • Start a context with the returned handler.
  • Prepare input to test against the rules into a ddwaf_object data structure.
  • Perform a run against the input in the context.
  • Optionally perform subsequent runs against additional input in the same context. Only newly relevant rules are checked against.
  • Discard context and/or handler as needed.

Example

The full example can be found here.

#include <yaml-cpp/yaml.h>
#include "ddwaf.h"

constexpr std::string_view waf_rule = R"(
version: "2.1"
rules:
  - id: "1"
    name: rule 1
    tags:
      type: flow1
      category: test
    conditions:
      - operator: match_regex
        parameters:
          inputs:
            - address: arg1
          regex: .*
      - operator: match_regex
        parameters:
          inputs:
            - address: arg2
          regex: .*
    on_match: [ block ]
)";

int main()
{
    YAML::Node doc = YAML::Load(waf_rule.data());

    auto rule = doc.as<ddwaf_object>();//= convert_yaml_to_args(doc);
    ddwaf_handle handle = ddwaf_init(&rule, nullptr, nullptr);
    ddwaf_object_free(&rule);
    if (handle == nullptr) {
        return EXIT_FAILURE;
    }

    ddwaf_context context = ddwaf_context_init(handle);
    if (context == nullptr) {
        ddwaf_destroy(handle);
        return EXIT_FAILURE;
    }

    ddwaf_object root, tmp;
    ddwaf_object_map(&root);
    ddwaf_object_map_add(&root, "arg1", ddwaf_object_string(&tmp, "string 1"));
    ddwaf_object_map_add(&root, "arg2", ddwaf_object_string(&tmp, "string 2"));

    ddwaf_result ret;
    auto code = ddwaf_run(context, &root, nullptr, &ret, 1000000 /* microseconds */);
    std::cout << "Output second run: " << code << '\n';
    if (code == DDWAF_MATCH) {
        YAML::Emitter out(std::cout);
        out.SetIndent(2);
        out.SetMapFormat(YAML::Block);
        out.SetSeqFormat(YAML::Block);
        out << object_to_yaml(ret.events);
        out << object_to_yaml(ret.actions);
    }

    ddwaf_result_free(&ret);

    ddwaf_context_destroy(context);
    ddwaf_destroy(handle);

    return EXIT_SUCCESS;
}

YAML to ddwaf::object converter example

namespace YAML {

template <>
struct as_if<ddwaf_object, void>
{
    explicit as_if(const Node& node_) : node(node_) {}

    static ddwaf_object yaml_to_object_helper(const Node& node)
    {
        ddwaf_object arg;
        switch (node.Type())
        {
            case NodeType::Sequence:
                ddwaf_object_array(&arg);
                break;
            case NodeType::Map:
                ddwaf_object_map(&arg);
                break;
            case NodeType::Scalar:
                ddwaf_object_string(&arg, node.Scalar().c_str());
                break;
            case NodeType::Null:
                ddwaf_object_null(&arg);
                break;
            case NodeType::Undefined:
            default:
                ddwaf_object_invalid(&arg);
                break;
        }
        return arg;
    }

    ddwaf_object operator()() const {
        std::list<std::tuple<ddwaf_object&, YAML::Node, YAML::Node::const_iterator>> stack;

        ddwaf_object root = yaml_to_object_helper(node);
        if (root.type == DDWAF_OBJ_MAP || root.type == DDWAF_OBJ_ARRAY) {
            stack.emplace_back(root, node, node.begin());
        }

        while (!stack.empty()) {
            auto current_depth = stack.size();
            auto &[parent_obj, parent_node, it] = stack.back();

            for (;it != parent_node.end(); ++it) {
                YAML::Node child_node = parent_node.IsMap() ? it->second : *it;
                auto child_obj = yaml_to_object_helper(child_node);
                if (parent_obj.type == DDWAF_OBJ_MAP) {
                    auto key = it->first.as<std::string>();
                    ddwaf_object_map_add(&parent_obj, key.c_str(), &child_obj);
                } else if (parent_obj.type == DDWAF_OBJ_ARRAY) {
                    ddwaf_object_array_add(&parent_obj, &child_obj);
                }

                if (child_obj.type == DDWAF_OBJ_MAP || child_obj.type == DDWAF_OBJ_ARRAY) {
                    auto &child_ptr = parent_obj.array[parent_obj.nbEntries - 1];
                    stack.emplace_back(child_ptr, child_node, child_node.begin());
                    ++it;
                    break;
                }
            }

            if (current_depth == stack.size()) { stack.pop_back(); }
        }
        return root;
    }

    const Node& node;
};

} // namespace YAML

ddwaf::object to YAML converter example

namespace {

YAML::Node object_to_yaml_helper(const ddwaf_object &obj)
{
    YAML::Node output;
    switch (obj.type) {
    case DDWAF_OBJ_BOOL:
        output = obj.boolean;
        break;
    case DDWAF_OBJ_SIGNED:
        output = obj.intValue;
        break;
    case DDWAF_OBJ_UNSIGNED:
        output = obj.uintValue;
        break;
    case DDWAF_OBJ_FLOAT:
        output = obj.f64;
        break;
    case DDWAF_OBJ_STRING:
        output = std::string{obj.stringValue, obj.nbEntries};
        break;
    case DDWAF_OBJ_MAP:
        output = YAML::Load("{}");
        break;
    case DDWAF_OBJ_ARRAY:
        output = YAML::Load("[]");
        break;
    case DDWAF_OBJ_INVALID:
    case DDWAF_OBJ_NULL:
        output = YAML::Null;
        break;
    };
    return output;
}

} // namespace

YAML::Node object_to_yaml(const ddwaf_object &obj)
{
    std::list<std::tuple<const ddwaf_object&, YAML::Node, std::size_t>> stack;

    YAML::Node root = object_to_yaml_helper(obj);
    if (obj.type == DDWAF_OBJ_MAP || obj.type == DDWAF_OBJ_ARRAY) {
        stack.emplace_back(obj, root, 0);
    }

    while (!stack.empty()) {
        auto current_depth = stack.size();
        auto &[parent_obj, parent_node, index] = stack.back();

        for (;index <parent_obj.nbEntries; ++index) {
            auto &child_obj = parent_obj.array[index];
            auto child_node = object_to_yaml_helper(child_obj);

            if (parent_obj.type == DDWAF_OBJ_MAP) {
                std::string key{child_obj.parameterName, child_obj.parameterNameLength};
                parent_node[key] = child_node;
            } else if (parent_obj.type == DDWAF_OBJ_ARRAY) {
                parent_node.push_back(child_node);
            }

            if (child_obj.type == DDWAF_OBJ_MAP || child_obj.type == DDWAF_OBJ_ARRAY) {
                stack.emplace_back(child_obj, child_node, 0);
                ++index;
                break;
            }
        }

        if (current_depth == stack.size()) {
            stack.pop_back();
        }
    }
    return root;
}

Example rule

The following rule:

version: 2.1
rules:
 - id: crs-042-001
   name: Detect a script tag
   tags:
     category: attack_attempt
     type: xss
   conditions:
    - operator: match_regex
      parameters:
        regex: "^<script>"
        inputs:
         - address: http.server.query

applied to the http.server.query value http://localhost/?q=<script>alert() hello world produces the following result:

[
  {
    "rule": {
      "id": "crs-042-001",
      "name": "Detect a script tag",
      "tags": {
        "category": "attack_attempt",
        "type": "xss"
      }
    },
    "rule_matches": [
      {
        "operator": "match_regex",
        "operator_value": "^<script>",
        "parameters": [
          {
            "address": "http.server.query",
            "key_path": [
              "q"
            ],
            "value": "<script>alert() hello world",
            "highlight": [
              "<script>"
            ]
          }
        ]
      }
    ]
  }
]

Binding implementation notes

See BINDING_IMPL_NOTES.md.

libddwaf's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

libddwaf's Issues

Segmentation fault in 1.6.2.0.0-x86_64-linux

Hi Team,

We're experiencing a segmentation fault each time we attempt to access a mounted engine's routes in Rails 7.0.4.3 using Ruby 3.1.3p185. Please see the stack below:

/app/vendor/bundle/ruby/3.1.0/gems/libddwaf-1.6.2.0.0-x86_64-linux/lib/datadog/appsec/waf.rb:655: [BUG] Segmentation fault at 0x0000007c167b39a9
ruby 3.1.3p185 (2022-11-24 revision 1a6b16756e) [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0032 p:---- s:0232 e:000231 CFUNC  :ddwaf_run
c:0031 p:0220 s:0224 e:000223 METHOD /app/vendor/bundle/ruby/3.1.0/gems/libddwaf-1.6.2.0.0-x86_64-linux/lib/datadog/appsec/waf.rb:655
c:0030 p:0051 s:0210 e:000209 METHOD /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/processor.rb:23
c:0029 p:0060 s:0199 E:0010b0 BLOCK  /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/reactive/response.rb:34
c:0028 p:0009 s:0190 e:000189 METHOD /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/reactive/subscriber.rb:14
c:0027 p:0010 s:0185 e:000184 BLOCK  /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/reactive/engine.rb:35 [FINISH]
c:0026 p:---- s:0181 e:000180 CFUNC  :each
c:0025 p:0037 s:0177 e:000176 BLOCK  /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/reactive/engine.rb:35 [FINISH]
c:0024 p:---- s:0171 e:000170 CFUNC  :each
c:0023 p:0031 s:0167 e:000166 METHOD /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/reactive/engine.rb:28
c:0022 p:0009 s:0161 e:000160 METHOD /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/reactive/operation.rb:35
c:0021 p:0010 s:0155 e:000154 BLOCK  /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/reactive/response.rb:17 [FINISH]
c:0020 p:---- s:0152 e:000151 CFUNC  :catch
c:0019 p:0006 s:0147 e:000146 METHOD /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/reactive/response.rb:16
c:0018 p:0058 s:0141 E:0009c0 BLOCK  /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/gateway/watcher.rb:94
c:0017 p:0066 s:0134 E:000978 METHOD /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/reactive/operation.rb:24 [FINISH]
c:0016 p:---- s:0127 e:000126 CFUNC  :new
c:0015 p:0036 s:0122 E:0013f8 BLOCK  /app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/contrib/rack/gateway/watcher.rb:73

via ddtrace-1.10.1:

/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/sampling/span/rule_parser.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/component.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/profiling/component.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/assets.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/processor.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/appsec/component.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/core/configuration/components.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/core/pin.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/core/configuration.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/core/extensions.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/core.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/registry.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/extensions.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/configuration/resolver.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/configuration/settings.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/configurable.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/patchable.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/registerable.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/integration.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/action_cable/ext.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/action_cable/configuration/settings.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/patcher.rb
/app/vendor/bundle/ruby/3.1.0/gems/ddtrace-1.10.1/lib/datadog/tracing/contrib/analytics.rb

We've successfully rolled back to 1.5.1.0.1-x86_64-linux, where this issue does not present.

The upgrade was brought in through ddtrace

ddtrace (1.9.0)
      libdatadog (~> 1.0.1.1.0)
      libddwaf (~> 1.5.1.0.0)
      ...
ddtrace (1.10.1)
      libdatadog (~> 2.0.0.1.0)
      libddwaf (~> 1.6.2.0.0)
      ...

Please let me know if there is any further information I can provide.

Thanks.

Adrian

Simplify rules based on a set of possible addresses

The WAF runs on logs processing with only a few addresses ever pushed. It would likely result in a substantial performance improvement if the rules could be stripped of all addresses that will never be pushed, and rules with no addresses left removed.

This could be implemented as a new option when rules are loaded, or, perhaps a worse option, as a tool to preprocess the rules file.

Add cross-compilation to arm64-darwin CI

This can be achieved on x86_64 macOS runners by adding -arch arm64 to compiler and linker flags.

Executing the tests is another matter but at least the builds would be automated.

Portable build segfaults on Ruby 2.6

Description

A crash is happening deep within ddwaf_context_run.

This crash has only been observed:

  • on Ruby 2.6 Docker Hub-based images, within a range of Ruby 2.1 to 3.1
  • on both x86_64 and aarch64
  • on both musl (ruby:2.6-alpine) and glibc (ruby:2.6)

It was never encountered:

  • during countless runs in development
  • in libddwaf-rb rspec runs
  • in relenv, where Ruby relenv apps run on Ruby 2.6.

The exact spec example failing varies, but it seems to be somewhat consistent as to which ones it may fail on, involving multiple runs, or runs with multiple results. Focusing on a single or a few spec examples does not seem to produce the result, it only seems to appear when running the full suite (more or less).

Notes:

  • There is only one thread (GC runs inline in Ruby)

  • This code does not reference Ruby-bound variables, all data should have been copied at this point

  • Every bit of data that is involved is reference-retained by libddwaf-rb at either the WAF::Handle or WAF::Context level, therefore since there is a hold on these references none should be garbage collected

  • ddwaf_objects are released upon WAF::Handle or WAF::Context` finalize (called at GC time)

  • It still crashes when doing the call while keping the GVL (using FFI blocking: false)

  • There may be a bug in libddwaf-rb where passing string size upon conversion to ddwaf_object is done using String#size instead of String#bytesize (FFI behaviour to be confirmed), but there is no multibyte data involved (including in the ruleset) so in this case String#size == String#bytesize:

    dd-trace-rb$ grep --color='auto' -P -n "[\x80-\xFF]" lib/datadog/appsec/assets/waf_rules/recommended.json || echo 'none'
    none
    

    Inputs typically being:

    { 'server.request.query' => { 'q' => '1 OR 1;' }, 'server.request.headers.no_cookies' => { 'user-agent' => 'Nessus SOAP' } }
    

    from:

    let(:input_sqli) { { 'server.request.query' => { 'q' => '1 OR 1;' } } }
    let(:input_scanner) { { 'server.request.headers.no_cookies' => { 'user-agent' => 'Nessus SOAP' } } }
    # ...
    let(:input) { input_scanner.merge(input_sqli) }
    
  • Upon crash, I was able to convert back ddwaf_objects involved using WAF.object_to_ruby in GDB

Reproduction

Reproducible on both x86_64 and aarch64. The following loop will reproduce the issue within 40 runs, often much less.

GNU (Debian-based)

git clone https://github.com/DataDog/dd-trace-rb.git
cd dd-trace-rb
docker run --rm -it -v "${PWD}":"${PWD}" -w "${PWD}" ruby:2.6 /bin/bash
gem install bundler:2.2.22
bundle install
bundle exec sh -c 'i=0; while true; do i=$(( $i + 1 )); echo "========== $i ========="; rspec spec/datadog/appsec/processor_spec.rb || exit 1; done'

musl (Alpine-based)

git clone https://github.com/DataDog/dd-trace-rb.git
cd dd-trace-rb
docker run --rm -it -v "${PWD}":"${PWD}" -w "${PWD}" ruby:2.6-alpine /bin/sh
apk add build-base git
gem install bundler:2.2.22
bundle install
bundle exec sh -c 'i=0; while true; do i=$(( $i + 1 )); echo "========== $i ========="; rspec spec/datadog/appsec/processor_spec.rb || exit 1; done'

Workaround

Creating two distinct builds, each linked respectively against glibc and musl, then dropping the resulting shared library in place in the corresponding container at the proper location allows for over 1000 runs to execute successfully.

git clone https://github.com/DataDog/libddwaf.git -b 1.3.0
git submodule update --init
docker run --rm -it -v "${PWD}":"${PWD}" -w "${PWD}" ruby:2.6 /bin/bash
apt-get update && apt-get install -y cmake
mkdir -p build && cd build
cmake ..
make -j4
docker ps # find repro container
container_id='949a7d2b5e69' docker cp build/libddwaf.so ${container_id}:/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so

Debugging with GDB

To investigate with GDB it's necessary to attach to the correct process. Unfortunately the toplevel items are shebang scripts and then there are multiple forks happening, so gdb <command> doesn't work quite as we'd want.

So I developed a GDB automation script (code below) that repeatedly watches for the different processes that are being run, attaches to each in turn, tell to continue execution, and if the process unexpectedly segfaults, loads libddwaf debug symbols and drop into an interactive GDB session ready to use, with extra Ruby helper GDB commands defined: rb-backtrace, rb-eval-string, rb-during-gc, pry, irb.

First we have to apply this patch on dd-trace-rb which helps the automation find the Ruby process that actually runs the tests:

diff --git a/spec/datadog/appsec/processor_spec.rb b/spec/datadog/appsec/processor_spec.rb
index 9cfe8718f..84a25846b 100644
--- a/spec/datadog/appsec/processor_spec.rb
+++ b/spec/datadog/appsec/processor_spec.rb
@@ -3,6 +3,14 @@
 require 'datadog/appsec/spec_helper'
 require 'datadog/appsec/processor'
 
+#require 'libddwaf'
+
+puts "= [#{Process.pid}] #{Time.now.to_i} waiting for GDB"
+$0 = "[GDB] " + $0
+sleep 1
+puts "= [#{Process.pid}] #{Time.now.to_i} running"
+puts "=" * 80
+
 RSpec.describe Datadog::AppSec::Processor do
   before do
     # libddwaf is not available yet for JRuby

Once applied, you can run the automation this way by attaching to the reproduction container from another terminal:

docker exec -it ${container_id} /bin/bash
apt-get install -y gdb
gdb --quiet --nx --eval-command="source gdb-auto.py"

And of course start the test loop from the original terminal:

bundle exec sh -c 'i=0; while true; do i=$(( $i + 1 )); echo "========== $i ========="; rspec spec/datadog/appsec/processor_spec.rb || exit 1; done'

Here's the gdb-auto.py automation script:

# gdb-auto.py
# gdb --quiet --nx --eval-command="source gdb-auto.py"

import os
import time
import sys

class RubyBacktrace(gdb.Command):
    def __init__(self):
        super(RubyBacktrace, self).__init__("rb-backtrace", gdb.COMMAND_USER)
        self.dont_repeat()

    def invoke(self, arg, from_tty):
        cmd = "call (void) rb_backtrace()"
        gdb.execute(cmd)

class RubyEvalString(gdb.Command):
    def __init__(self):
        super(RubyEvalString, self).__init__("rb-eval-string", gdb.COMMAND_USER)
        self.dont_repeat()

    def invoke(self, arg, from_tty):
        argv = gdb.string_to_argv(arg)
        ruby = argv[0]

        cmd = r'call (void) rb_p((unsigned long) rb_eval_string_protect("%s", (int*)0))' % ruby

        gdb.execute(cmd)

class RubyDuringGC(gdb.Command):
    def __init__(self):
        super(RubyDuringGC, self).__init__("rb-during-gc", gdb.COMMAND_USER)
        self.dont_repeat()

    def invoke(self, arg, from_tty):
        argv = gdb.string_to_argv(arg)

        cmd = "call (int) rb_during_gc()"

        gdb.execute(cmd)

class Pry(gdb.Command):
    def __init__(self):
        super(Pry, self).__init__("pry", gdb.COMMAND_USER)
        self.dont_repeat()

    def invoke(self, arg, from_tty):
        argv = gdb.string_to_argv(arg)
        ruby = "require 'pry'; binding.pry"

        cmd = r'call (void) rb_p((unsigned long) rb_eval_string_protect("%s", (int*)0))' % ruby

        gdb.execute(cmd)

class IRB(gdb.Command):
    def __init__(self):
        super(IRB, self).__init__("irb", gdb.COMMAND_USER)
        self.dont_repeat()

    def invoke(self, arg, from_tty):
        argv = gdb.string_to_argv(arg)
        ruby = "require 'irb'; binding.irb"

        cmd = r'call (void) rb_p((unsigned long) rb_eval_string_protect("%s", (int*)0))' % ruby

        gdb.execute(cmd)

RubyBacktrace()
RubyEvalString()
RubyDuringGC()
Pry()
IRB()

try:
    gdb.execute("set pagination off")
    gdb.execute("set print pretty")
    gdb.execute("set print thread-events")

    while True:
        now = int(time.time())
        print("= [none] %s waiting..." % (now,), end='', flush=True)
        pid = ''
        while True:
            pid = os.popen("pgrep -f '^\[GDB\] '").read().rstrip()
            if pid != "":
                break

            time.sleep(0.1)
            print(".", end='', flush=True)
        print("")

        now = int(time.time())
        print("= [%s] %s found pid" % (pid, now))

        try:
            now = int(time.time())
            print("= [%s] %s attaching" % (pid, now))
            gdb.execute("attach %s" % pid)
        except gdb.error as e: # Unable to attach
            now = int(time.time())
            print("= [%s] %s failed to attach: %s" % (pid, now, e))
            gdb.execute("quit")

        gdb.execute("info threads")

        now = int(time.time())
        print("= [%s] %s continuing" % (pid, now))
        gdb.execute("continue")
        now = int(time.time())
        print("= [%s] %s continued" % (pid, now))

        # drop interactive if segfault
        status = gdb.execute("info program", False, True)
        now = int(time.time())
        print("= [%s] %s status: %r" % (pid, now, status))
        if 'Program stopped' in status:
            for objfile in gdb.objfiles():
                if objfile.filename.endswith("libddwaf.so"):
                    now = int(time.time())
                    print("= [%s] %s loading symbols for %s from %s" % (pid, now, objfile.filename, "tmp/libddwaf-1.3.0-linux-x86_64/lib/.build-id/62/7d8958d72440eb020e55ecb8772503796a73af.debug"))
                    objfile.add_separate_debug_file("tmp/libddwaf-1.3.0-linux-x86_64/lib/.build-id/62/7d8958d72440eb020e55ecb8772503796a73af.debug")
                    now = int(time.time())
                    print("= [%s] %s loaded symbols for %s from %s" % (pid, now, objfile.filename, "tmp/libddwaf-1.3.0-linux-x86_64/lib/.build-id/62/7d8958d72440eb020e55ecb8772503796a73af.debug"))

            break

        try:
            now = int(time.time())
            print("= [%s] %s detaching" % (pid, now))
            #gdb.execute("detach")
        except gdb.error as e: # program exited successfully
            print("failed to detach from %s: %s" % (pid, e))
except KeyboardInterrupt:
    print('')
    print("= [none] %s interrupt: quitting" % (now))
    gdb.execute("quit")

Logs

Ruby segfault report

/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/waf.rb:471: [BUG] Segmentation fault at 0x0000000000002430
ruby 2.6.10p210 (2022-04-12 revision 67958) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0084 p:---- s:0423 e:000422 CFUNC  :ddwaf_run
c:0083 p:0152 s:0415 e:000414 METHOD /usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/waf.rb:471
c:0082 p:0028 s:0405 e:000404 METHOD /Users/lloeki/Source/github.com/DataDog/dd-trace-rb/lib/datadog/appsec/processor.rb:34
c:0081 p:0016 s:0396 e:000395 BLOCK  /Users/lloeki/Source/github.com/DataDog/dd-trace-rb/spec/datadog/appsec/processor_spec.rb:265 [FINISH]
c:0080 p:---- s:0393 e:000392 CFUNC  :initialize
c:0079 p:---- s:0390 e:000389 CFUNC  :new
c:0078 p:0016 s:0385 e:000384 BLOCK  /Users/lloeki/Source/github.com/DataDog/dd-trace-rb/spec/datadog/appsec/processor_spec.rb:265 [FINISH]

-- Ruby level backtrace information ----------------------------------------
/Users/lloeki/Source/github.com/DataDog/dd-trace-rb/spec/datadog/appsec/processor_spec.rb:265:in `block (4 levels) in <top (required)>'
/Users/lloeki/Source/github.com/DataDog/dd-trace-rb/spec/datadog/appsec/processor_spec.rb:265:in `new'
/Users/lloeki/Source/github.com/DataDog/dd-trace-rb/spec/datadog/appsec/processor_spec.rb:265:in `initialize'
/Users/lloeki/Source/github.com/DataDog/dd-trace-rb/spec/datadog/appsec/processor_spec.rb:265:in `block (5 levels) in <top (required)>'
/Users/lloeki/Source/github.com/DataDog/dd-trace-rb/lib/datadog/appsec/processor.rb:34:in `run'
/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/waf.rb:471:in `run'
/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/waf.rb:471:in `ddwaf_run'

-- Machine register context ------------------------------------------------
 RIP: 0x00007fced529d6e1 RBP: 0x0000000000002430 RSP: 0x00007ffc0f323070
 RAX: 0x0000000000000000 RBX: 0x00007ffc0f323120 RCX: 0x0000000000000001
 RDX: 0x0000000000000001 RDI: 0x00007ffc0f323120 RSI: 0x0000000000002430
  R8: 0x000055bf79435960  R9: 0x6f6e2e7372656461 R10: 0x0000000000000000
 R11: 0xa3db81c9af6b0f70 R12: 0x00007ffc0f323120 R13: 0x0000000000000001
 R14: 0x000055bf7927ac18 R15: 0x000055bf792c48d0 EFL: 0x0000000000010202

-- C level backtrace information -------------------------------------------
/usr/local/lib/libruby.so.2.6(rb_print_backtrace+0x11) [0x7fced841442a] vm_dump.c:715
/usr/local/lib/libruby.so.2.6(rb_vm_bugreport) vm_dump.c:985
/usr/local/lib/libruby.so.2.6(rb_bug_context+0xec) [0x7fced8264cec] error.c:609
/usr/local/lib/libruby.so.2.6(sigsegv+0x42) [0x7fced837b752] signal.c:998
/lib/x86_64-linux-gnu/libc.so.6(0x7fced8013d60) [0x7fced8013d60]
/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/../../../vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so(0x7fced529d6e1) [0x7fced529d6e1]
/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/../../../vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so(0x7fced529ba24) [0x7fced529ba24]
/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/../../../vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so(0x7fced52a4a22) [0x7fced52a4a22]
/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/../../../vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so(0x7fced5299a8a) [0x7fced5299a8a]
/usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/../../../vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so(ddwaf_run+0x6f) [0x7fced5298eef]
/usr/lib/x86_64-linux-gnu/libffi.so.7(0x7fced5851d1d) [0x7fced5851d1d]
/usr/lib/x86_64-linux-gnu/libffi.so.7(0x7fced5851289) [0x7fced5851289]
/usr/local/bundle/gems/ffi-1.15.5/lib/ffi_c.so(call_blocking_function+0x19) [0x7fced586e379] Call.c:336
/usr/local/lib/libruby.so.2.6(rb_native_mutex_lock+0x0) [0x7fced83c2a58] thread.c:1442
/usr/local/lib/libruby.so.2.6(unblock_function_clear) thread.c:479
/usr/local/lib/libruby.so.2.6(blocking_region_end) thread.c:1411
/usr/local/lib/libruby.so.2.6(call_without_gvl) thread.c:1442
/usr/local/lib/libruby.so.2.6(rb_thread_call_without_gvl) thread.c:1556
/usr/local/bundle/gems/ffi-1.15.5/lib/ffi_c.so(rbffi_do_blocking_call+0x1c) [0x7fced586e34c] Call.c:344
/usr/local/lib/libruby.so.2.6(rb_vrescue2+0xc7) [0x7fced8269677] eval.c:917
/usr/local/lib/libruby.so.2.6(rb_rescue2+0x8a) [0x7fced826c5ca] eval.c:987
/usr/local/bundle/gems/ffi-1.15.5/lib/ffi_c.so(rbffi_CallFunction+0x1fc) [0x7fced586f37c] Call.c:387
/usr/local/bundle/gems/ffi-1.15.5/lib/ffi_c.so(custom_trampoline+0x23) [0x7fced5872963] MethodHandle.c:220
/usr/local/lib/libruby.so.2.6(vm_cfp_consistent_p+0x0) [0x7fced83f115b] vm_insnhelper.c:1908
/usr/local/lib/libruby.so.2.6(vm_call_cfunc_with_frame) vm_insnhelper.c:1910
/usr/local/lib/libruby.so.2.6(vm_call_cfunc) vm_insnhelper.c:1924
/usr/local/lib/libruby.so.2.6(vm_exec_core+0x148) [0x7fced83fba38] insns.def:765
/usr/local/lib/libruby.so.2.6(rb_vm_exec+0x155) [0x7fced8401665] vm.c:1885

GDB session with debug symbols

  Id   Target Id                               Frame 
* 1    Thread 0x7f0b69687ec0 (LWP 2363) "ruby" 0x00007f0b699ce49b in __ppoll (fds=fds@entry=0x7fff08251c38, nfds=nfds@entry=1, timeout=<optimized out>, timeout@entry=0x7fff08251cf0, sigmask=sigmask@entry=0x0) at ../sysdeps/unix/sysv/linux/ppoll.c:44
= [2363] 1652974810 continuing

Program received signal SIGSEGV, Segmentation fault.
0x00007f0b66ba06e1 in ?? () from /usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/../../../vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so
= [2363] 1652974812 continued
= [2363] 1652974812 status: '\tUsing the running image of attached Thread 0x7f0b69687ec0 (LWP 2363).\nProgram stopped at 0x7f0b66ba06e1.\nIt stopped with signal SIGSEGV, Segmentation fault.\n'
= [2363] 1652974812 loading symbols for /usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/../../../vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so from tmp/libddwaf-1.3.0-linux-x86_64/lib/.build-id/62/7d8958d72440eb020e55ecb8772503796a73af.debug
= [2363] 1652974812 loaded symbols for /usr/local/bundle/gems/libddwaf-1.3.0.1.0-x86_64-linux/lib/datadog/appsec/../../../vendor/libddwaf/libddwaf-1.3.0-linux-x86_64/lib/libddwaf.so from tmp/libddwaf-1.3.0-linux-x86_64/lib/.build-id/62/7d8958d72440eb020e55ecb8772503796a73af.debug
(gdb) bt
#0  std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >::__is_long (this=0xe0) at /muslsysroot/include/c++/v1/string:1423
#1  0x00007f0b66b9ea24 in std::__1::unordered_set<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::hash<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::equal_to<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > >::find (this=0x7fff0824d520, __k=...) at /muslsysroot/include/c++/v1/unordered_set:681
#2  PWManifest::findImpactedArgs (this=0x55891f84b050, newFields=..., argsImpacted=...) at /build/src/libddwaf/src/PWManifest.cpp:70
#3  0x00007f0b66ba7a22 in PWRetriever::addParameter (this=0x0, input=...) at /build/src/libddwaf/src/PWRetriever.cpp:483
#4  0x00007f0b66b9ca8a in PWAdditive::run (this=0x55891f466940, newParameters=..., res=..., timeLeft=0) at /build/src/libddwaf/src/PWAdditive.cpp:63
#5  0x00007f0b66b9beef in ddwaf_run (context=0x7fff0824d520, data=<optimized out>, result=<optimized out>, timeout=1) at /build/src/libddwaf/src/PowerWAFInterface.cpp:155
#6  0x00007f0b67154d1d in ?? () from /usr/lib/x86_64-linux-gnu/libffi.so.7
#7  0x00007f0b67154289 in ?? () from /usr/lib/x86_64-linux-gnu/libffi.so.7
#8  0x00007f0b67171379 in call_blocking_function (data=<optimized out>) at Call.c:336
#9  0x00007f0b69cc5a58 in call_without_gvl (fail_if_interrupted=<optimized out>, data2=<optimized out>, ubf=<optimized out>, data1=<optimized out>, func=<optimized out>) at thread.c:1442
#10 rb_thread_call_without_gvl (func=func@entry=0x7f0b67171360 <call_blocking_function>, data1=data1@entry=0x7fff0824db10, ubf=ubf@entry=0xffffffffffffffff, data2=data2@entry=0x0) at thread.c:1556
#11 0x00007f0b6717134c in rbffi_do_blocking_call (data=data@entry=140733330021136) at Call.c:344
#12 0x00007f0b69b6c677 in rb_vrescue2 (b_proc=0x7f0b67171330 <rbffi_do_blocking_call>, data1=140733330021136, r_proc=0x7f0b67171320 <rbffi_save_frame_exception>, data2=140733330021360, args=args@entry=0x7fff0824da30) at eval.c:917
#13 0x00007f0b69b6f5ca in rb_rescue2 (b_proc=b_proc@entry=0x7f0b67171330 <rbffi_do_blocking_call>, data1=data1@entry=140733330021136, r_proc=r_proc@entry=0x7f0b67171320 <rbffi_save_frame_exception>, data2=data2@entry=140733330021360) at eval.c:987
#14 0x00007f0b6717237c in rbffi_CallFunction (argc=<optimized out>, argv=<optimized out>, function=<optimized out>, fnInfo=0x55891f3eac40) at Call.c:387
#15 0x00007f0b67175963 in custom_trampoline (argc=<optimized out>, argv=<optimized out>, self=<optimized out>, handle=<optimized out>) at MethodHandle.c:220
#16 0x00007f0b69cf415b in vm_call_cfunc_with_frame (cc=<optimized out>, cc=<optimized out>, ci=0x55891f2e9830, calling=<optimized out>, reg_cfp=0x7f0b6962fd40, ec=0x55891ce6b9e8) at vm_insnhelper.c:1908
#17 vm_call_cfunc (ec=0x55891ce6b9e8, reg_cfp=0x7f0b6962fd40, calling=<optimized out>, ci=0x55891f2e9830, cc=<optimized out>) at vm_insnhelper.c:1924

Stripped release artefacts with separate debug information

The current release artefacts are very big and some Go users complained about their size. dd-trace-go currently strip the unneeded symbols and the entire debug information in a script but we believe the build artefacts published by this repository should do it by:

  1. stripping the library at link time
  2. still providing the debug information but in a separate file (the commonly found *.dbg files)

The resulting files are up to 10x smaller (eg. for linux/amd64, from 31MB to 3MB), especially for cases with the LLVM libc++ library which includes the entire standard C++ library with its debug information.

pm compliance with CRS' behavior

There is a small mistake I made when implementing @pm.
The CRS operator is case insensitive while ours (as far as I remember) isn’t.
I mostly worked around that by injecting a lowercase transformer but that might be something to look at

WAF API for gRPC

I think it's worth logging here the fact that the current integration of the WAF for gRPC is pretty hacky: in order to be able to trigger the same rules multiple times, we instantiate one WAF context per WAF run. This is due to the fact this is the first time we encounter addresses that can happen multiple times during the WAF context lifetime.

A new API should somehow allow running the WAF with new address values that were already sent before.

Note that it also currently has the issue of keeping the memory used by the values for the time of the WAF context, so this new API should also handle this.

This hack is currently described in our gRPC RFC at https://datadoghq.atlassian.net/wiki/spaces/APS/pages/2278064284/gRPC+Protocol+Support#Implementation-Milestones

Duplicate matches in output

The WAF is producing multiple duplicate matches for certain rules within the same flow.

[
  {
    "rule": {
      "id": "nfd-000-002",
      "name": "Detect failed attempt to fetch readme files",
      "tags": {
        "type": "security_scanner",
        "category": ""
      }
    },
    "rule_matches": [
      {
        "operator": "match_regex",
        "operator_value": "^404$",
        "parameters": [
          {
            "address": "server.response.status",
            "key_path": [],
            "value": "404",
            "highlight": [
              "404"
            ]
          }
        ]
      },
      {
        "operator": "match_regex",
        "operator_value": "^404$",
        "parameters": [
          {
            "address": "server.response.status",
            "key_path": [],
            "value": "404",
            "highlight": [
              "404"
            ]
          }
        ]
      },
      {
        "operator": "match_regex",
        "operator_value": "readme\\.[\\.a-z0-9]+$",
        "parameters": [
          {
            "address": "server.request.uri.raw",
            "key_path": [],
            "value": "/readme.txt",
            "highlight": [
              "readme.txt"
            ]
          }
        ]
      }
    ]
  }
]

Not building on Mac OS X

Hello,
I have been maintaining the conda feedstock of ddtrace here https://github.com/conda-forge/ddtrace-feedstock.

And the latest version of ddtrace ( v0.58.* ) is not building for Mac OS. For instance see a log here.
The reason is that the TARGET_OS_OSX env variable is not defined.

image

I have been looking at the issue and it seems that is because the building of re2 fails when building libddwaf.

It seems that the re2 problem has been fixed already see this commit so I think fixing the issue might be a matter of updating dependencies.

I have also opened an issue in ddtrace here
Thanks !

Invalid object vs null object

Make possible to call the WAF with:

  1. A JSON like { "key": null }
  2. A native object like { "key": <a native value whose type cannot be represented into a WAF object> }

For 1, for sure it means we need to introduce the null value representation in the current WAF format.
For 2, I'm unsure about using the null value as it looses the information "it wasn't null, but an unsupported value". The current invalid object could be possibly used for that.

But as of today, unsupported values imply the WAF flows gets aborted when they reach them, and even invalidates the previously matching flows (IIRC, to be confirmed). IMO, this makes useless to call the WAF when you have an invalid value somewhere in the object that will be checked by a flow. I would simplify this by either removing the invalid value, or making it useful for 2, without aborting the WAF flows when reached. To be further challenged with more use-cases.

Happy to give a hand if needed

WAF Proxy: defensive body parsing: consider validating & parsing payloads in the WAF

The current agent implementation (e.g: DataDog/datadog-agent#19654) parses payload content when possible before passing it to the WAF, which may open it up for exploits of vulnerabilities in the various parsers (JSON, url-encoded, mime/multiplart, ...).

Since most of these vulnerabilities stem from careless processing of untrusted data, I believe it would be ideal if the WAF received the raw payload bytes + associated headers, so it could validate or sanitize the payloads before determining whether they are deemed "safe to parse", and eventually parsing them to inspect the contents.

One drawback obviously is that this requires the WAF to ship with the various required parsers (which may significantly inflate the image size), and it probably should favor highly defensive implementations the parsers (which often comes at a significant performance cost). In order to mitigate the first of these problems, an alternative could be for the WAF to expose a function that inspects the raw payload data + content-type headers, and returns a sanitized form of it, or a flag indicating whether that payload may be parsed or not -- this way the parsers can continue to reside in the agent (possibly being provided by the standard library) instead of being hoisted/duplicated into the WAF.

Integer representation

The integer representation has been an opened topic for the past 3 years and source of integration problems way many times.

I personally think we should make the API trivial by letting libddwaf users pass ddwaf objects of type DDWAF_OBJ_SIGNED or DDWAF_OBJ_UNSIGNED, and have libddwaf internally handle them, such as converting it to a string when used in the regexp operator.

The latest problem I experienced with this was the new min_length rule option that is a JSON number while the WAF expects it as a string.

Overall, I'd be okay in saying we should pass them as string, but then maybe we should remove the signed/unsigned object types to avoid those mistakes again.

Stop returning JSON blobs

From @Julio-Guerra in #12 (comment)

(And feel free to stop returning a json blob ;-p)

The libraries need to reach into the result, so this means there's a serialisation that gets deserialized right after. A JSON serializer will run again on the full event payload, and in the current case at the very least needs to manipulate and escape the JSON blob string. For the sake of consistency with the input this could conveniently return a ddwaf::object.

Dropping the JSON parser dependency would then be possible.

Windows package naming

The naming of the windows packages generated with msvc and mingw64 should be improved as it's currently not obvious which package corresponds to which build. For reference, win32 and x64 packages are built with msvc and x86_64 is build with mingw64.

Report timeout on rule matches

The latest version of the WAF does not provide a mechanism by which to determine if a run has timed-out in the event of rules being matched, as the timeout return value is overridden by the block or monitor return value. A new timeout flag should be added to ddwaf_result to provide this information.

Align x86_64 "universal" linux build with aarch64

There's an asymmetry with x86_64:

  • there's libddwaf-1.0.11-linux-x86_64-static.tar.gz which is a static lib that should work on alpine and debian but that I can't use for the Ruby bindings since I'm using FFI which needs a shared lib
  • there's libddwaf-1.0.11-linux-x86_64-muslc.tar.gz and libddwaf-1.0.11-linux-x86_64-glibc.tar.gz which do have shared libs but don't run on both, and should be fine but are a bit awkward as to being different for another cpu arch but the difference is in fact unrelated to the cpu arch, just that it turns out it's using a different build process.

I have the feeling that libddwaf-1.0.11-linux-x86_64-static.tar.gz should drop the -static from the name and reuse/follow as much as possible the build process of libddwaf-1.0.11-linux-aarch64.tar.gz, resulting in a static+shared lib build for x86_64, uniform with aarch64. The build should be very similar expect without the ARM toolchain, so probably using the regular compiler instead of the cross compiling one should be fine, and then tests would run without qemu.

We could duplicate the aarch64 dir as x86_64 and proceed with the adjustments, to "make it work" and surface possible commonalities. A future step would look into sharing stuff to "make it right".

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.