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 5c3f3e5..0000000 Binary files a/spec/faketime/libfaketime.so and /dev/null differ