From e12c5bbdfba5945470f47c987aebcaf048d66935 Mon Sep 17 00:00:00 2001 From: aeris Date: Tue, 26 Apr 2022 16:40:24 +0200 Subject: [PATCH] Mock getaddrinfo --- Makefile | 18 +-- bin/cryptcheck | 2 +- bin/rspec.sh | 7 ++ spec/fake.rb | 46 ++++++++ spec/fake/fake.c | 216 +++++++++++++++++++++++++++++++++++ spec/fake/fake.h | 7 ++ spec/fake/test.c | 82 +++++++++++++ spec/faketime.rb | 27 ----- spec/faketime/faketime.c | 35 ------ spec/faketime/faketime.h | 2 - spec/faketime/libfaketime.so | Bin 8264 -> 0 bytes 11 files changed, 369 insertions(+), 73 deletions(-) create mode 100755 bin/rspec.sh create mode 100644 spec/fake.rb create mode 100644 spec/fake/fake.c create mode 100644 spec/fake/fake.h create mode 100644 spec/fake/test.c delete mode 100644 spec/faketime.rb delete mode 100644 spec/faketime/faketime.c delete mode 100644 spec/faketime/faketime.h delete mode 100755 spec/faketime/libfaketime.so diff --git a/Makefile b/Makefile index 866e105..280da1c 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ ROOT_DIR := $(dir $(realpath $(firstword $(MAKEFILE_LIST)))) BUILD_DIR := $(ROOT_DIR)/build export RBENV_ROOT ?= $(ROOT_DIR)/build/rbenv -RBENV_VERSION := v1.2.0 +_RBENV_VERSION := v1.2.0 RUBY_BUILD_VERSION = v20220218 OPENSSL_1_0_VERSION := 1.0.2j @@ -39,7 +39,7 @@ clean: rm -rf build/ $(RBENV_ROOT)/: - git clone https://github.com/rbenv/rbenv/ "$@" -b "$(RBENV_VERSION)" --depth 1 + git clone https://github.com/rbenv/rbenv/ "$@" -b "$(_RBENV_VERSION)" --depth 1 $(RBENV_ROOT)/plugins/ruby-build/: | $(RBENV_ROOT)/ git clone https://github.com/rbenv/ruby-build/ "$@" -b "$(RUBY_BUILD_VERSION)" --depth 1 rbenv: | $(RBENV_ROOT)/plugins/ruby-build/ @@ -107,16 +107,18 @@ ruby-1.0: $(RBENV_ROOT)/versions/$(RUBY_1_0_VERSION)-cryptcheck ruby-1.1: $(RBENV_ROOT)/versions/$(RUBY_1_1_VERSION)-cryptcheck ruby: ruby-1.0 ruby-1.1 -build/libfaketime.so: spec/faketime/faketime.c spec/faketime/faketime.h - $(CC) $^ -o "$@" -shared -fPIC -ldl -std=c99 -Werror -Wall -faketime: build/libfaketime.so -.PHONY: faketime +build/libfake.so: spec/fake/fake.c spec/fake/fake.h + LANG=C $(CC) $^ -g -o "$@" -shared -fPIC -ldl -std=c99 -Werror -Wall -pedantic +fake: build/libfake.so + +build/test: spec/fake/test.c + LANG=C $(CC) $^ -g -o "$@" -Werror -Wall -pedantic test-material: bin/generate-test-material.rb -test: spec/faketime/libfaketime.so - LD_LIBRARY_PATH="$(LIBRARY_PATH_1_0):$(BUILD_DIR)" bin/rspec +test: build/libfake.so + LD_LIBRARY_PATH="$(LIBRARY_PATH_1_0):$(BUILD_DIR)" LD_PRELOAD="$(ROOT_DIR)/$^" bin/rspec .PHONY: test docker-1.0: diff --git a/bin/cryptcheck b/bin/cryptcheck index 73de025..9d15c7e 100755 --- a/bin/cryptcheck +++ b/bin/cryptcheck @@ -2,7 +2,7 @@ require 'rubygems' require 'bundler/setup' require 'thor' -require 'awesome_print' +require 'amazing_print' begin require 'pry-byebug' rescue LoadError diff --git a/bin/rspec.sh b/bin/rspec.sh new file mode 100755 index 0000000..20f6454 --- /dev/null +++ b/bin/rspec.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -e +eval "$(bin/load-rbenv "$1")" +BASE_DIR="$(dirname "$(dirname "$0")")" +shift +LD_PRELOAD="$BASE_DIR/build/libfake.so" LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$BASE_DIR/build/" \ + exec rspec "$@" diff --git a/spec/fake.rb b/spec/fake.rb new file mode 100644 index 0000000..80b994e --- /dev/null +++ b/spec/fake.rb @@ -0,0 +1,46 @@ +require 'ffi' + +module Fake + extend FFI::Library + ffi_lib 'fake' + + def self.freeze(time) + self._freeze time.to_i + return yield + ensure + self._unfreeze + end + require 'amazing_print' + + def self.getaddrinfo(host, *ips) + args = ips.collect { |ip| [:string, ip] } + self._mock_getaddrinfo host, ips.size, *args.flatten + return yield + ensure + self._unmock_getaddrinfo + end + + private + + def self._freeze(_) + #This is a stub, used for indexing + end + + def self._unfreeze + #This is a stub, used for indexing + end + + attach_function :_freeze, [:time_t], :void + attach_function :_unfreeze, [], :void + + def self._mock_getaddrinfo(_, _, *_) + #This is a stub, used for indexing + end + + def self._unmock_getaddrinfo + #This is a stub, used for indexing + end + + attach_function :_mock_getaddrinfo, [:string, :size_t, :varargs], :void + attach_function :_unmock_getaddrinfo, [], :void +end diff --git a/spec/fake/fake.c b/spec/fake/fake.c new file mode 100644 index 0000000..5fc7105 --- /dev/null +++ b/spec/fake/fake.c @@ -0,0 +1,216 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fake.h" + +bool frozen = false; +time_t frozen_time = 0; +static time_t (*libc_time)(time_t*); + +void _freeze(const time_t time) { + frozen_time = time; + frozen = true; +} + +void _unfreeze() { + frozen = false; +} + +time_t time(time_t *arg) { + if (frozen) { + if (arg) *arg = frozen_time; + return frozen_time; + } else { + time_t time = libc_time(arg); + return time; + } +} + +typedef struct ip_t { + int af; + void *addr; +} ip_t; +typedef struct host_t { + char *host; + size_t size; + ip_t **ips; +} host_t; +host_t *mocked_host = NULL; + +static int (*libc_getaddrinfo)(const char*, const char*, + const struct addrinfo *, struct addrinfo **); + +static void free_mocked_host() { + if (mocked_host == NULL) return; + for (size_t n =0; n < mocked_host->size; ++n ) { + ip_t *ip = mocked_host->ips[n]; + free(ip->addr); + free(ip); + } + free(mocked_host->ips); + free(mocked_host->host); + free(mocked_host); + mocked_host = NULL; +} + +void _mock_getaddrinfo(const char *host, const size_t size, ...) { + free_mocked_host(); + + host_t *mocked = malloc(sizeof(*mocked)); + mocked->host = strdup(host); + mocked->size = size; + ip_t **ips = malloc(size * sizeof(*ips)); + + va_list args; + va_start(args, size); + + for (size_t n = 0; n < size; ++n) { + char *arg = va_arg(args, char*); + ip_t *ip = malloc(sizeof(*ip)); + int family; + void *addr, *saddr; + if (strstr(arg, ".") != NULL) { + family = AF_INET; + struct sockaddr_in *s4 = malloc(sizeof(*s4)); + s4->sin_family = family; + s4->sin_port = 0; + saddr = s4; + addr = &(s4->sin_addr); + } else if (strstr(arg, ":") != NULL) { + family = AF_INET6; + struct sockaddr_in6 *s6 = malloc(sizeof(*s6)); + s6->sin6_family = family; + s6->sin6_port = 0; + s6->sin6_flowinfo = 0; + s6->sin6_scope_id = 0; + saddr = s6; + addr = &(s6->sin6_addr); + } else { + fprintf(stderr, "Invalid IP format: %s\n", arg); + exit(-1); + } + inet_pton(family, arg, addr); + + ip->af = family; + ip->addr = saddr; + + ips[n] = ip; + } + + va_end(args); + + mocked->ips = ips; + mocked_host = mocked; +} + +void _unmock_getaddrinfo() { + free_mocked_host(); +} + +typedef struct socktype_t { + int type; + int protocol; +} socktype_t; +const socktype_t SOCK_TYPES[] = { + { .type = SOCK_STREAM, .protocol = IPPROTO_TCP }, + { .type = SOCK_DGRAM, .protocol = IPPROTO_UDP }, + { .type = SOCK_RAW, .protocol = IPPROTO_IP } +}; +const size_t SOCK_TYPES_SIZE = sizeof(SOCK_TYPES)/sizeof(socktype_t); + +int get_port_from_service(const char *service) { + if (service == NULL) return 0; + + struct servent* servent = getservbyname(service, NULL); + int port; + if (servent == NULL) { + port = htons(atoi(service)); + } else { + port = servent->s_port; + free(servent); + } + + return port; +} + +int getaddrinfo(const char *node, const char *service, + const struct addrinfo *hints, struct addrinfo **res) { + const int port = get_port_from_service(service); + + if (mocked_host == NULL || strcmp(node, mocked_host->host) != 0) + return libc_getaddrinfo(node, service, hints, res); + + size_t size = mocked_host->size; + struct addrinfo *addr = NULL, *previous = NULL; + for (size_t n = 0; n < size; ++n) { + const ip_t *ip = mocked_host->ips[n]; + + for ( size_t m = 0; m < SOCK_TYPES_SIZE; ++m) { + const socktype_t type = SOCK_TYPES[m]; + + if ( (hints->ai_socktype != 0 && hints->ai_socktype != type.type) || + (hints->ai_protocol != 0 && hints->ai_protocol != type.protocol) ) { + continue; + } + + addr = malloc(sizeof(*addr)); + addr->ai_flags = hints->ai_flags; + addr->ai_family = ip->af; + addr->ai_socktype = type.type; + addr->ai_protocol = type.protocol; + + size_t len; + void *ai_addr; + switch (addr->ai_family) { + case AF_INET:; + struct sockaddr_in *ai_addr4; + len = sizeof(*ai_addr4); + ai_addr4 = malloc(len); + ai_addr = ai_addr4; + memcpy(ai_addr4, ip->addr, len); + ai_addr4->sin_port = port; + break; + case AF_INET6:; + struct sockaddr_in6 *ai_addr6; + len = sizeof(*ai_addr6); + ai_addr6 = malloc(len); + ai_addr = ai_addr6; + memcpy(ai_addr6, ip->addr, len); + ai_addr6->sin6_port = port; + break; + } + addr->ai_addrlen = len; + addr->ai_addr = ai_addr; + addr->ai_canonname = NULL; + addr->ai_next = previous; + previous = addr; + } + } + + *res = addr; + + return 0; +} + +static void __attribute__((constructor)) setup() { + char *error = dlerror(); + *(void **) (&libc_time) = dlsym(RTLD_NEXT, "time"); + if ((error = dlerror()) != NULL) { + fprintf(stderr, "%s\n", error); + } + *(void **) (&libc_getaddrinfo) = dlsym(RTLD_NEXT, "getaddrinfo"); + if ((error = dlerror()) != NULL) { + fprintf(stderr, "%s\n", error); + } +} + +static void __attribute__((destructor)) teardown() { + free_mocked_host(); +} diff --git a/spec/fake/fake.h b/spec/fake/fake.h new file mode 100644 index 0000000..49b8dfd --- /dev/null +++ b/spec/fake/fake.h @@ -0,0 +1,7 @@ +#include + +void _freeze(const time_t time); +void _unfreeze(); + +void _mock_getaddrinfo(const char *host, const size_t size, ...); +void _unmock_getaddrinfo(); diff --git a/spec/fake/test.c b/spec/fake/test.c new file mode 100644 index 0000000..1be6821 --- /dev/null +++ b/spec/fake/test.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include +#include + +int debug(const char *fmt, ...) { +// return 0; + va_list arg; + va_start(arg, fmt); + int result = vfprintf(stderr, fmt, arg); + va_end(arg); + fflush(stderr); + return result; +} + +void debug_saddr(const int family, const void *saddr) { + char buff[INET6_ADDRSTRLEN]; + const void *addr; + switch (family) { + case AF_INET:; + const struct sockaddr_in *s4 = saddr; + debug(" family: %d\n", s4->sin_family); + debug(" port: %d\n", s4->sin_port); + addr = &(s4->sin_addr); + break; + case AF_INET6:; + const struct sockaddr_in6 *s6 = saddr; + debug(" family: %d\n", s6->sin6_family); + debug(" port: %d\n", s6->sin6_port); + debug(" flowinfo: %d\n", s6->sin6_flowinfo); + debug(" scope: %d\n", s6->sin6_scope_id); + addr = &(s6->sin6_addr); + break; + } + + inet_ntop(family, addr, buff, INET6_ADDRSTRLEN); + debug(" addr: %p, %s\n", saddr, buff); +} + +void debug_addr(const struct addrinfo *addr) { + debug("addrinfo: %p\n", addr); + debug(" flags: %d\n", addr->ai_flags); + debug(" family: %d\n", addr->ai_family); + debug(" type: %d\n", addr->ai_socktype); + debug(" protocol: %d\n", addr->ai_protocol); + debug(" len: %d\n", addr->ai_addrlen); + if (addr->ai_addrlen > 0) debug_saddr(addr->ai_family, addr->ai_addr); + debug(" name: %s\n", addr->ai_canonname); + debug(" next: %p\n", addr->ai_next); +} + +void debug_addrinfo(const struct addrinfo *addr) { + for (const struct addrinfo *rp = addr; rp != NULL; rp = rp->ai_next) { + debug_addr(rp); + } +} + +int main(int argc, char *argv[]) { + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; /* Datagram socket */ + hints.ai_flags = 0; /* For wildcard IP address */ + hints.ai_protocol = 0; /* Any protocol */ + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + struct addrinfo *result; + debug("Before result: result=%p\n", &result); + const int s = getaddrinfo("localhost", NULL, &hints, &result); + if (s != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + exit(EXIT_FAILURE); + } + + debug("The result: result=%p\n", result); + debug_addrinfo(result); +} diff --git a/spec/faketime.rb b/spec/faketime.rb deleted file mode 100644 index 51e2a11..0000000 --- a/spec/faketime.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'ffi' - -module FakeTime - extend FFI::Library - ffi_lib 'faketime' - - def self._freeze(_) - #This is a stub, used for indexing - end - def self.unfreeze - #This is a stub, used for indexing - end - - attach_function :_freeze, [:ulong], :void - attach_function :unfreeze, [], :void - - def self.freeze(time) - self._freeze time.to_i - if block_given? - begin - return yield - ensure - self.unfreeze - end - end - end -end diff --git a/spec/faketime/faketime.c b/spec/faketime/faketime.c deleted file mode 100644 index 86812fa..0000000 --- a/spec/faketime/faketime.c +++ /dev/null @@ -1,35 +0,0 @@ -#define _GNU_SOURCE -#include -#include "time.h" -#include "faketime.h" - -char frozen = 0; -time_t frozen_time = 0; - -typedef time_t (*orig_time_f_type)(time_t*); -orig_time_f_type orig_time = NULL; - -void _freeze(unsigned long time) { - frozen_time = (time_t)time; - frozen = 1; -} - -void unfreeze() { - frozen = 0; -} - -time_t time(time_t *arg) { - if (orig_time == NULL) { - orig_time = (orig_time_f_type) dlsym(RTLD_NEXT, "time"); - } - - if (frozen) { - if (arg) { - *arg = frozen_time; - } - return frozen_time; - } else { - time_t time = orig_time(arg); - return time; - } -} diff --git a/spec/faketime/faketime.h b/spec/faketime/faketime.h deleted file mode 100644 index b9af8d8..0000000 --- a/spec/faketime/faketime.h +++ /dev/null @@ -1,2 +0,0 @@ -void _freeze(unsigned long time); -void unfreeze(); diff --git a/spec/faketime/libfaketime.so b/spec/faketime/libfaketime.so deleted file mode 100755 index 5c3f3e5f1a5431ef258a145f112fe062d7a8f9c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8264 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