iguana icon indicating copy to clipboard operation
iguana copied to clipboard

Assertion in from_xml using iguana::parse_escape_xml

Open kost opened this issue 2 months ago • 0 comments

Summary

iguana::parse_escape_xml decodes XML numeric entities into UTF-8 without validating the upper bound. Entities above 0x10FFFF trip an assertion in encode_utf8, aborting the process and enabling a trivial denial of service for consumers of the XML reader.

Versions

Versions tested and affected:

  • 1.09
  • latest git master
$ git rev-parse HEAD
4b6fb4dfe96485d727f5e5cb5bff7b8e2a62783c

Build and test platform

Ubuntu 24.04.3

Test case

$ clang++ -std=c++20 -g -O1 -fsanitize=address -I. \
    -o tool/pocs/poc_driver tool/pocs/poc_driver.cpp
$ ASAN_OPTIONS=detect_leaks=0:abort_on_error=0 \
    ./tool/pocs/poc_driver xml xml_invalid_entity.xml
#include <cstdint>
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <sstream>
#include <string>
#include <vector>

#include "iguana/json_reader.hpp"
#include "iguana/xml_reader.hpp"
#include "iguana/yaml_reader.hpp"

namespace poc {

struct NetworkRoute {
  std::string address;
  std::vector<int> ports;
  bool secure;
};
YLT_REFL(NetworkRoute, address, ports, secure);

struct Credential {
  std::string user;
  std::optional<std::string> secret;
  std::vector<uint8_t> salt;
};
YLT_REFL(Credential, user, secret, salt);

struct Telemetry {
  std::vector<double> metrics;
  std::vector<float> readings;
  std::optional<std::string> last_error;
};
YLT_REFL(Telemetry, metrics, readings, last_error);

struct ServiceConfig {
  std::string name;
  std::map<int, Credential> fallback_credentials;
  std::vector<NetworkRoute> routes;
  Telemetry telemetry;
};
YLT_REFL(ServiceConfig, name, fallback_credentials, routes, telemetry);

struct SerConfig {
  std::string name;
};
YLT_REFL(SerConfig, name);

std::string read_file(const std::string &path) {
  std::ifstream ifs(path, std::ios::binary);
  if (!ifs) {
    throw std::runtime_error("failed to open " + path);
  }
  std::ostringstream oss;
  oss << ifs.rdbuf();
  return oss.str();
}

}  // namespace poc

int main(int argc, char **argv) {
  if (argc != 3) {
    std::cerr << "Usage: " << argv[0]
              << " <json|xml|yaml-map|yaml-str> <path>\n";
    return 1;
  }

  const std::string mode = argv[1];
  const std::string path = argv[2];
  const std::string payload = poc::read_file(path);

  try {
    if (mode == "json") {
      poc::ServiceConfig cfg;
      iguana::from_json(cfg, payload);
    } else if (mode == "xml") {
      poc::SerConfig cfg;
      iguana::from_xml(cfg, payload);
    } else if (mode == "yaml-map") {
      poc::ServiceConfig cfg;
      iguana::from_yaml(cfg, payload);
    } else if (mode == "yaml-str") {
      poc::ServiceConfig cfg;
      iguana::from_yaml(cfg, payload);
    } else {
      std::cerr << "unknown mode: " << mode << "\n";
      return 1;
    }
  } catch (const std::exception &ex) {
    std::cerr << "parser threw: " << ex.what() << "\n";
    return 0;
  }

  std::cout << "parsed successfully\n";
  return 0;
}

PoC input: xml_invalid_entity.xml

xml_invalid_entity.xml

Latest git master

Confirmed by aborted run (assert inside ASan build):

poc_driver: ./iguana/detail/utf.hpp:46: void iguana::encode_utf8(...):
Assertion `codepoint <= 0x10FFFF' failed.
#0 iguana::encode_utf8<char, std::string>
#1 iguana::parse_escape_xml(...)
#2 iguana::detail::xml_parse_value(...)
#3 iguana::from_xml<poc::SerConfig>(...)

kost avatar Oct 26 '25 10:10 kost