diff --git a/src/scopeelf.c b/src/scopeelf.c index 9aaa13667..f80a8abbf 100644 --- a/src/scopeelf.c +++ b/src/scopeelf.c @@ -11,9 +11,6 @@ #include "fn.h" #include "scopeelf.h" -#define GOPCLNTAB_MAGIC_112 0xfffffffb -#define GOPCLNTAB_MAGIC_116 0xfffffffa - void freeElf(char *buf, size_t len) { @@ -336,123 +333,6 @@ getSymbol(const char *buf, char *sname) return (void *)symaddr; } -void * -getGoSymbol(const char *buf, char *sname) -{ - int i; - Elf64_Addr symaddr = 0; - Elf64_Ehdr *ehdr; - Elf64_Shdr *sections; - const char *section_strtab = NULL; - const char *sec_name = NULL; - - if (!buf || !sname) return NULL; - - ehdr = (Elf64_Ehdr *)buf; - sections = (Elf64_Shdr *)((char *)buf + ehdr->e_shoff); - section_strtab = (char *)buf + sections[ehdr->e_shstrndx].sh_offset; - - for (i = 0; i < ehdr->e_shnum; i++) { - sec_name = section_strtab + sections[i].sh_name; - if (scope_strcmp(".gopclntab", sec_name) == 0) { - const void *pclntab_addr = buf + sections[i].sh_offset; - /* - Go symbol table is stored in the .gopclntab section - More info: https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub - */ - uint32_t magic = *((const uint32_t *)(pclntab_addr)); - if (magic == GOPCLNTAB_MAGIC_112) { - uint64_t sym_count = *((const uint64_t *)(pclntab_addr + 8)); - const void *symtab_addr = pclntab_addr + 16; - - for(i=0; ie_shoff); - section_strtab = (char *)buf + sections[ehdr->e_shstrndx].sh_offset; - const char magic[0xe] = "\xff Go buildinf:"; - void *go_build_ver_addr = NULL; - - for (i = 0; i < ehdr->e_shnum; i++) { - sec_name = section_strtab + sections[i].sh_name; - sec_data = (const char *)buf + sections[i].sh_offset; - // Since go1.13, the .go.buildinfo section has been added to - // identify where runtime.buildVersion exists, for the case where - // go apps have been stripped of their symbols. - - // offset into sec_data field contents - // ----------------------------------------------------------- - // 0x0 build info magic = "\xff Go buildinf:" - // 0xe binary ptrSize - // 0xf endianness - // 0x10 pointer to string runtime.buildVersion - // 0x10 + ptrSize pointer to runtime.modinfo - // 0x10 + 2 * ptr size pointer to build flags - - if (!scope_strcmp(sec_name, ".go.buildinfo") && - (sections[i].sh_size >= 0x18) && - (!scope_memcmp(&sec_data[0], magic, sizeof(magic))) && - (sec_data[0xe] == 0x08) && // 64 bit executables only - (sec_data[0xf] == 0x00)) { // little-endian - - uint64_t *addressPtr = (uint64_t*)&sec_data[0x10]; - - go_build_ver_addr = (void*)*addressPtr; - } - } - - return go_build_ver_addr; -} - bool is_static(char *buf) { diff --git a/src/scopeelf.h b/src/scopeelf.h index 6eaa037fc..a89c5cc27 100644 --- a/src/scopeelf.h +++ b/src/scopeelf.h @@ -26,8 +26,6 @@ int doGotcha(struct link_map *, got_list_t *, Elf64_Rela *, Elf64_Sym *, char *, int getElfEntries(struct link_map *, Elf64_Rela **, Elf64_Sym **, char **, int *rsz); Elf64_Shdr* getElfSection(char *, const char *); void * getSymbol(const char *, char *); -void * getGoSymbol(const char *, char *); -void * getGoVersionAddr(const char*); bool is_static(char *); bool is_go(char *); bool is_musl(char *); diff --git a/src/wrap_go.c b/src/wrap_go.c index c29a162b9..a64bb9086 100644 --- a/src/wrap_go.c +++ b/src/wrap_go.c @@ -16,30 +16,17 @@ #include "capstone/capstone.h" #include "scopestdlib.h" -#define SCOPE_STACK_SIZE (size_t)(32 * 1024) -//#define ENABLE_SIGNAL_MASKING_IN_SYSEXEC 1 -#define ENABLE_CAS_IN_SYSEXEC 1 +#define GOPCLNTAB_MAGIC_112 0xfffffffb +#define GOPCLNTAB_MAGIC_116 0xfffffffa +#define GOPCLNTAB_MAGIC_118 0xfffffff0 -#define SWITCH_ENV "SCOPE_SWITCH" -#define SWITCH_USE_NO_THREAD "no_thread" -#define SWITCH_USE_THREAD "thread" +#define SCOPE_STACK_SIZE (size_t)(32 * 1024) #define EXIT_STACK_SIZE (32 * 1024) #define UNKNOWN_GO_VER (-1) #define MIN_SUPPORTED_GO_VER (8) #define PARAM_ON_REG_GO_VER (19) -#ifdef ENABLE_CAS_IN_SYSEXEC -#include "atomic.h" -#else -// This disables the CAS spinlocks by default. Aint nobody got time for that. -bool -atomicCasU64(uint64_t* ptr, uint64_t oldval, uint64_t newval) -{ - return TRUE; -} -#endif - // compile-time control for debugging #define NEEDEVNULL 1 //#define funcprint sysprint @@ -50,6 +37,7 @@ atomicCasU64(uint64_t* ptr, uint64_t oldval, uint64_t newval) #define UNDEF_OFFSET (-1) int g_go_major_ver = UNKNOWN_GO_VER; +static char g_go_build_ver[7]; enum index_hook_t { INDEX_HOOK_WRITE = 0, @@ -223,7 +211,8 @@ go_schema_t go_17_schema = { [INDEX_HOOK_TLS_SERVER_WRITE] = {"net/http.checkConnErrorWriter.Write", go_hook_reg_tls_write, NULL, 0}, // tls server write [INDEX_HOOK_TLS_CLIENT_READ] = {"net/http.(*persistConn).readResponse", go_hook_reg_readResponse, NULL, 0}, // tls client read [INDEX_HOOK_TLS_CLIENT_WRITE] = {"net/http.persistConnWriter.Write", go_hook_reg_pc_write, NULL, 0}, // tls client write - [INDEX_HOOK_EXIT] = {"runtime.exit.abi0", go_hook_exit, NULL, 0}, +// [INDEX_HOOK_EXIT] = {"runtime.exit.abi0", go_hook_exit, NULL, 0}, + [INDEX_HOOK_EXIT] = {"runtime.exit", go_hook_exit, NULL, 0}, [INDEX_HOOK_DIE] = {"runtime.dieFromSignal", go_hook_die, NULL, 0}, [INDEX_HOOK_MAX] = {"TAP_TABLE_END", NULL, NULL, 0} }, @@ -244,6 +233,8 @@ go_schema_t *g_go_schema = &go_16_schema; // overridden if later version #define GO_HOOK_TLS_SERVER_WRITE (g_go_schema->tap[INDEX_HOOK_TLS_SERVER_WRITE].assembly_fn) #define GO_HOOK_TLS_CLIENT_READ (g_go_schema->tap[INDEX_HOOK_TLS_CLIENT_READ].assembly_fn) #define GO_HOOK_TLS_CLIENT_WRITE (g_go_schema->tap[INDEX_HOOK_TLS_CLIENT_WRITE].assembly_fn) +#define GO_HOOK_EXIT (g_go_schema->tap[INDEX_HOOK_EXIT].assembly_fn) +#define GO_HOOK_DIE (g_go_schema->tap[INDEX_HOOK_DIE].assembly_fn) uint64_t g_glibc_guard = 0LL; @@ -257,43 +248,244 @@ devnull(const char* fmt, ...) } #endif -// c_str() is provided to convert a go-style string to a c-style string. -// The resulting c_str will need to be passed to free() when it is no +// Use go_str() whenever a "go string" type needs to be interpreted. +// The resulting go_str will need to be passed to free_go_str() when it is no // longer needed. -static char * -c_str(gostring_t *go_str) -{ - if (!go_str || go_str->len <= 0) return NULL; - - char *path; - if ((path = scope_calloc(1, go_str->len+1)) == NULL) return NULL; - scope_memmove(path, go_str->str, go_str->len); - path[go_str->len] = '\0'; - - return path; -} - +// Don't use go_str() for byte arrays. static char * go_str(void *go_str) { - // Go 17 and higher use null terminated strings instead of a string and a length + // Go 17 and higher use "c style" null terminated strings instead of a string and a length if (g_go_major_ver > 16) { // We need to deference the address first before casting to a char * if (!go_str) return NULL; return (char *)*(uint64_t *)go_str; } - return c_str((gostring_t *)go_str); + + gostring_t* go_str_tmp = (gostring_t *)go_str; + if (!go_str_tmp || go_str_tmp->len <= 0) return NULL; + + char *c_str; + if ((c_str = scope_calloc(1, go_str_tmp->len+1)) == NULL) return NULL; + scope_memmove(c_str, go_str_tmp->str, go_str_tmp->len); + c_str[go_str_tmp->len] = '\0'; + + return c_str; } static void free_go_str(char *str) { - // Go 17 and higher use null terminated strings instead of a string and a length + // Go 17 and higher use "c style" null terminated strings instead of a string and a length if (g_go_major_ver > 16) { return; } if(str) scope_free(str); } +static bool +match_assy_instruction(void *addr, char *mnemonic) +{ + csh dhandle = 0; + cs_arch arch; + cs_mode mode; + cs_insn *asm_inst = NULL; + unsigned int asm_count = 0; + uint64_t size = 32; + bool rc = FALSE; + +#if defined(__aarch64__) + arch = CS_ARCH_ARM64; + mode = CS_MODE_LITTLE_ENDIAN; +#elif defined(__x86_64__) + arch = CS_ARCH_X86; + mode = CS_MODE_64; +#else + return FALSE; +#endif + + if (cs_open(arch, mode, &dhandle) != CS_ERR_OK) return FALSE; + + asm_count = cs_disasm(dhandle, addr, size, (uint64_t)addr, 0, &asm_inst); + if (asm_count <= 0) return FALSE; + + if (!scope_strcmp((const char*)asm_inst->mnemonic, mnemonic)) rc = TRUE; + + if (asm_inst) cs_free(asm_inst, asm_count); + cs_close(&dhandle); + + return rc; +} + +static void * +getGoVersionAddr(const char* buf) +{ + int i; + Elf64_Ehdr *ehdr; + Elf64_Shdr *sections; + const char *section_strtab = NULL; + const char *sec_name; + const char *sec_data; + + ehdr = (Elf64_Ehdr *)buf; + sections = (Elf64_Shdr *)(buf + ehdr->e_shoff); + section_strtab = (char *)buf + sections[ehdr->e_shstrndx].sh_offset; + const char magic[0xe] = "\xff Go buildinf:"; + void *go_build_ver_addr = NULL; + + for (i = 0; i < ehdr->e_shnum; i++) { + sec_name = section_strtab + sections[i].sh_name; + sec_data = (const char *)buf + sections[i].sh_offset; + // Since go1.13, the .go.buildinfo section has been added to + // identify where runtime.buildVersion exists, for the case where + // go apps have been stripped of their symbols. + + // offset into sec_data field contents + // ----------------------------------------------------------- + // 0x0 build info magic = "\xff Go buildinf:" + // 0xe binary ptrSize + // 0xf endianness + // 0x10 pointer to string runtime.buildVersion + // 0x10 + ptrSize pointer to runtime.modinfo + // 0x10 + 2 * ptr size pointer to build flags + + if (!scope_strcmp(sec_name, ".go.buildinfo") && + (sections[i].sh_size >= 0x18) && + (!scope_memcmp(&sec_data[0], magic, sizeof(magic))) && + (sec_data[0xe] == 0x08)) { // 64 bit executables only + + // debug/buildinfo/buildinfo.go + // If the endianness has the 2 bit set, then the pointers are zero + // and the 32-byte header is followed by varint-prefixed string data + // for the two string values we care about. + if (sec_data[0xf] == 0x00) { // little-endian + uint64_t *addressPtr = (uint64_t*)&sec_data[0x10]; + go_build_ver_addr = (void*)*addressPtr; + } else if (sec_data[0xf] == 0x02) { + scope_memmove(g_go_build_ver, (char*)&sec_data[0x21], 6); + g_go_build_ver[6] = '\0'; + go_build_ver_addr = &g_go_build_ver; + } + } + } + return go_build_ver_addr; +} + +static void * +getGoSymbol(const char *buf, char *sname, char *altname, char *mnemonic) +{ + int i; + Elf64_Addr symaddr = 0; + Elf64_Ehdr *ehdr; + Elf64_Shdr *sections; + const char *section_strtab = NULL; + const char *sec_name = NULL; + + if (!buf || !sname) return NULL; + + ehdr = (Elf64_Ehdr *)buf; + sections = (Elf64_Shdr *)((char *)buf + ehdr->e_shoff); + section_strtab = (char *)buf + sections[ehdr->e_shstrndx].sh_offset; + + for (i = 0; i < ehdr->e_shnum; i++) { + sec_name = section_strtab + sections[i].sh_name; + if (scope_strcmp(".gopclntab", sec_name) == 0) { + const void *pclntab_addr = buf + sections[i].sh_offset; + /* + Go symbol table is stored in the .gopclntab section + More info: https://docs.google.com/document/d/1lyPIbmsYbXnpNj57a261hgOYVpNRcgydurVQIyZOz_o/pub + */ + uint32_t magic = *((const uint32_t *)(pclntab_addr)); + if (magic == GOPCLNTAB_MAGIC_112) { + uint64_t sym_count = *((const uint64_t *)(pclntab_addr + 8)); + const void *symtab_addr = pclntab_addr + 16; + + for(i=0; ibuf, "runtime.buildVersion"); - if (!go_ver) { - //runtime.buildVersion symbol not found, probably dealing with a stripped binary - //try to retrieve the version symbol address from the .go.buildinfo section - go_ver = getGoVersionAddr(ebuf->buf); - } //check ELF type Elf64_Ehdr *ehdr = (Elf64_Ehdr *)ebuf->buf; // if it's a position independent executable, get the base address from /proc/self/maps @@ -686,13 +872,25 @@ initGoHook(elf_buf_t *ebuf) sysprint("base %lx %lx %lx\n", base, (uint64_t)ebuf->text_addr, textSec->sh_offset); base = base - (uint64_t)ebuf->text_addr + textSec->sh_offset; } - - go_ver = (void *) ((uint64_t)go_ver + base); - - if (go_ver && (go_runtime_version = c_str(go_ver))) { + void *go_ver_sym = getSymbol(ebuf->buf, "runtime.buildVersion"); + if (!go_ver_sym) { + // runtime.buildVersion symbol not found, probably dealing with a stripped binary + // try to retrieve the version symbol address from the .go.buildinfo section + // if g_go_build_ver is set we know we're dealing with a char * + // if it is not set, we know we're dealing with a "go string" + void *ver_addr = getGoVersionAddr(ebuf->buf); + if (g_go_build_ver[0] != '\0') { + go_ver = (char *)((uint64_t)ver_addr + base); + } else { + go_ver = go_str((void *)((uint64_t)ver_addr + base)); + } + } else { + go_ver = go_str((void *)((uint64_t)go_ver_sym + base)); + } + + if (go_ver && (go_runtime_version = go_ver)) { sysprint("go_runtime_version = %s\n", go_runtime_version); - g_go_major_ver = go_major_version(go_runtime_version); } if (g_go_major_ver < MIN_SUPPORTED_GO_VER) { @@ -728,14 +926,15 @@ initGoHook(elf_buf_t *ebuf) if (g_go_major_ver > 16) { // Use the abi0 I/F for syscall based functions in Go >= 1.17 if (((go_runtime_cgocall = getSymbol(ebuf->buf, "runtime.asmcgocall.abi0")) == 0) && - ((go_runtime_cgocall = getGoSymbol(ebuf->buf, "runtime.asmcgocall.abi0")) == 0)) { - sysprint("ERROR: can't get the address for runtime.cgocall\n"); + ((go_runtime_cgocall = getGoSymbol(ebuf->buf, "runtime.asmcgocall.abi0", + "runtime.asmcgocall", "mov")) == 0)) { + sysprint("ERROR: can't get the address for runtime.cgocall.abi0\n"); return; // don't install our hooks } } else { // Use the native abi - the only abi present in <= Go 1.16 if (((go_runtime_cgocall = getSymbol(ebuf->buf, "runtime.asmcgocall")) == 0) && - ((go_runtime_cgocall = getGoSymbol(ebuf->buf, "runtime.asmcgocall")) == 0)) { + ((go_runtime_cgocall = getGoSymbol(ebuf->buf, "runtime.asmcgocall", NULL, NULL)) == 0)) { sysprint("ERROR: can't get the address for runtime.cgocall\n"); return; // don't install our hooks } @@ -779,7 +978,7 @@ initGoHook(elf_buf_t *ebuf) // Look for the symbol in the ELF symbol table if (((orig_func = getSymbol(ebuf->buf, tap->func_name)) == NULL) && // Otherwise look in the .gopclntab section - ((orig_func = getGoSymbol(ebuf->buf, tap->func_name)) == NULL)) { + ((orig_func = getGoSymbol(ebuf->buf, tap->func_name, NULL, NULL)) == NULL)) { sysprint("ERROR: can't get the address for %s\n", tap->func_name); continue; } @@ -792,7 +991,7 @@ initGoHook(elf_buf_t *ebuf) orig_func = (void *) ((uint64_t)orig_func + base); asm_count = cs_disasm(disass_handle, orig_func, size, - (uint64_t)orig_func, 0, &asm_inst); + (uint64_t)orig_func, 0, &asm_inst); if (asm_count <= 0) { sysprint("ERROR: disassembler fails: %s\n\tlen %" PRIu64 " code %p result %lu\n\ttext addr %p text len %zu oinfotext 0x%" PRIx64 "\n", tap->func_name, size, @@ -924,14 +1123,12 @@ static void c_write(char *stackaddr) { uint64_t fd = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_write_fd); - char *buf = go_str((void *)(stackaddr + g_go_schema->arg_offsets.c_write_buf)); + char *buf = (char *)*(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_write_buf); uint64_t rc = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_write_rc); uint64_t initialTime = getTime(); funcprint("Scope: write fd %ld rc %ld buf %s\n", fd, rc, buf); doWrite(fd, initialTime, (rc != -1), buf, rc, "go_write", BUF, 0); - - free_go_str(buf); } EXPORTON void * @@ -1036,7 +1233,7 @@ static void c_read(char *stackaddr) { uint64_t fd = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_read_fd); - char *buf = go_str((void *)(stackaddr + g_go_schema->arg_offsets.c_read_buf)); + char *buf = (char *)*(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_read_buf); uint64_t rc = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_read_rc); uint64_t initialTime = getTime(); @@ -1044,8 +1241,6 @@ c_read(char *stackaddr) funcprint("Scope: read of %ld rc %ld\n", fd, rc); doRead(fd, initialTime, (rc != -1), buf, rc, "go_read", BUF, 0); - - free_go_str(buf); } EXPORTON void * @@ -1127,13 +1322,13 @@ c_http_server_read(char *stackaddr) uint64_t connReader = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_server_read_connReader); if (!connReader) return; // protect from dereferencing null - char *buf = go_str((void *)(stackaddr + g_go_schema->arg_offsets.c_http_server_read_buf)); + char *buf = (char *)*(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_server_read_buf); // buf len 0x18 // buf cap 0x20 uint64_t rc = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_server_read_rc); uint64_t cr_conn_rwc_if, cr_conn_rwc, netFD, pfd; - uint64_t conn = *(uint64_t *)(connReader + g_go_schema->struct_offsets.connReader_to_conn); + uint64_t conn = *(uint64_t *)(connReader + g_go_schema->struct_offsets.connReader_to_conn); if (!conn) return; // protect from dereferencing null cr_conn_rwc_if = conn + g_go_schema->struct_offsets.conn_to_rwc; @@ -1165,8 +1360,6 @@ c_http_server_read(char *stackaddr) doProtocol((uint64_t)0, fd, buf, rc, TLSRX, BUF); } } - - free_go_str(buf); } EXPORTON void * @@ -1195,7 +1388,7 @@ c_http_server_write(char *stackaddr) int fd = -1; uint64_t conn = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_server_write_conn); if (!conn) return; // protect from dereferencing null - char *buf = go_str((void *)(stackaddr + g_go_schema->arg_offsets.c_http_server_write_buf)); + char *buf = (char *)*(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_server_write_buf); uint64_t rc = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_server_write_rc); uint64_t w_conn_rwc_if, w_conn_rwc, netFD, pfd; @@ -1216,8 +1409,6 @@ c_http_server_write(char *stackaddr) doProtocol((uint64_t)0, fd, buf, rc, TLSTX, BUF); } } - - free_go_str(buf); } EXPORTON void * @@ -1248,7 +1439,7 @@ c_http_client_write(char *stackaddr) int fd = -1; uint64_t w_pc = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_client_write_w_pc); - char *buf = go_str((void *)(stackaddr + g_go_schema->arg_offsets.c_http_client_write_buf)); + char *buf = (char *)*(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_client_write_buf); uint64_t rc = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_client_write_rc); uint64_t pc_conn_if, w_pc_conn, netFD, pfd; @@ -1273,8 +1464,6 @@ c_http_client_write(char *stackaddr) doProtocol((uint64_t)0, fd, buf, rc, TLSRX, BUF); funcprint("Scope: c_http_client_write of %d\n", fd); } - - free_go_str(buf); } EXPORTON void * @@ -1306,7 +1495,7 @@ c_http_client_read(char *stackaddr) int fd = -1; stackaddr += g_go_schema->arg_offsets.c_http_client_read_callee; - uint64_t pc = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_client_read_pc); + uint64_t pc = *(uint64_t *)(stackaddr + g_go_schema->arg_offsets.c_http_client_read_pc); uint64_t pc_conn_if, pc_conn, netFD, pfd, pc_br, len = 0; char *buf = NULL; @@ -1327,7 +1516,7 @@ c_http_client_read(char *stackaddr) } if ((pc_br = *(uint64_t *)(pc + g_go_schema->struct_offsets.persistConn_to_bufrd)) != 0) { - buf = go_str((void *)(pc_br + g_go_schema->struct_offsets.bufrd_to_buf)); + buf = (char *)*(uint64_t *)(pc_br + g_go_schema->struct_offsets.bufrd_to_buf); // len is part of the []byte struct; the func doesn't return a len len = *(uint64_t *)(pc_br + 0x08); } @@ -1336,8 +1525,6 @@ c_http_client_read(char *stackaddr) doProtocol((uint64_t)0, fd, buf, len, TLSRX, BUF); funcprint("Scope: c_http_client_read of %d\n", fd); } - - free_go_str(buf); } } @@ -1374,7 +1561,7 @@ c_exit(char *stackaddr) // don't use stackaddr; patch_first_instruction() does not provide // frame_size, so stackaddr isn't usable - funcprint("c_exit"); + funcprint("c_exit\n"); int i; struct timespec ts = {.tv_sec = 0, .tv_nsec = 10000}; // 10 us @@ -1403,11 +1590,11 @@ c_exit(char *stackaddr) EXPORTON void * go_exit(char *stackptr) { - return do_cfunc(stackptr, c_exit, go_hook_exit); + return do_cfunc(stackptr, c_exit, GO_HOOK_EXIT); } EXPORTON void * go_die(char *stackptr) { - return do_cfunc(stackptr, c_exit, go_hook_die); + return do_cfunc(stackptr, c_exit, GO_HOOK_DIE); } diff --git a/test/integration/README.md b/test/integration/README.md index 95ba4ac63..1bcc101c9 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -60,7 +60,7 @@ AppScope Integration Test Runner `make (test)-shell` - run a shell in the test's container `make (test)-exec` - run a shell in the existing test's container `make (test)-build` - build the test's image -Tests: alpine attach-glibc attach-musl awsnuke bash cli console detect_proto elastic glibc gogen go_2 go_3 go_4 go_5 go_6 go_7 go_8 go_9 go_10 go_11 go_12 go_13 go_14 go_15 go_16 go_17 http java7 java8 java9 java10 java11 java12 java13 java14 kafka logstream nginx oracle-java7 oracle-java8 oracle-java9 oracle-java10 oracle-java11 musl oracle-java12 oracle-java13 oracle-java14 service-initd service-systemd splunk syscalls syscalls-alpine tls transport +Tests: alpine attach-glibc attach-musl awsnuke bash cli console detect_proto elastic glibc gogen go_2 go_3 go_4 go_5 go_6 go_7 go_8 go_9 go_10 go_11 go_12 go_13 go_14 go_15 go_16 go_17 http java7 java8 java9 java10 java11 java12 java13 java14 kafka logstream nginx oracle-java7 oracle-java8 oracle-java9 oracle-java10 oracle-java11 musl oracle-java12 oracle-java13 oracle-java14 service-initd service-systemd splunk syscalls syscalls-alpine terraform tls transport ``` Developers can run tests locally pretty easily with this setup. We use it for diff --git a/test/integration/awsnuke/scope-test b/test/integration/awsnuke/scope-test index a5b4d7bba..69471d51a 100755 --- a/test/integration/awsnuke/scope-test +++ b/test/integration/awsnuke/scope-test @@ -1,4 +1,4 @@ -#! /bin/bash +#!/bin/bash DEBUG=0 # set this to 1 to capture the EVT_FILE for each test diff --git a/test/integration/docker-compose.x86_64.yml b/test/integration/docker-compose.x86_64.yml index 3713f28d2..806f816c7 100644 --- a/test/integration/docker-compose.x86_64.yml +++ b/test/integration/docker-compose.x86_64.yml @@ -458,3 +458,12 @@ services: context: . dockerfile: ./awsnuke/Dockerfile <<: *scope-common + terraform: + image: ghcr.io/criblio/appscope-test-terraform:${TAG:?Missing TAG environment variable} + build: + cache_from: + - ghcr.io/criblio/appscope-test-terraform:${TAG:?Missing TAG environment variable} + context: . + dockerfile: ./terraform/Dockerfile + <<: *scope-common + diff --git a/test/integration/go/Dockerfile b/test/integration/go/Dockerfile index 0f70725db..c2452561f 100644 --- a/test/integration/go/Dockerfile +++ b/test/integration/go/Dockerfile @@ -22,7 +22,9 @@ RUN cd /go/syscalls && \ RUN mkdir /go/net COPY ./go/net/plainServer.go /go/net RUN cd /go/net && \ - go build -buildmode=pie -o plainServerDynamic plainServer.go + go build -buildmode=pie -o plainServerDynamicPie plainServer.go +RUN cd /go/net && \ + go build -o plainServerDynamic plainServer.go RUN cd /go/net && \ CGO_ENABLED=0 go build -o plainServerStatic plainServer.go COPY ./go/net/tlsServer.go /go/net @@ -43,6 +45,8 @@ RUN cd /go/net &&\ go build -o plainClientDynamic plainClient.go RUN cd /go/net && \ CGO_ENABLED=0 go build -o plainClientStatic plainClient.go +RUN cd /go/net && \ + CGO_ENABLED=0 go build -ldflags='-s' -o plainClientStaticStripped plainClient.go COPY ./go/net/tlsClient.go /go/net RUN cd /go/net && \ go build -o tlsClientDynamic tlsClient.go @@ -69,7 +73,7 @@ COPY ./go/influx/influxdb-selfsigned.key /etc/ssl/. COPY ./go/influx/influxdb-selfsigned.crt /etc/ssl/. ENV SCOPE_CRIBL_ENABLE=false -ENV SCOPE_LOG_LEVEL=error +ENV SCOPE_LOG_LEVEL=warning ENV SCOPE_LOG_DEST=file:///tmp/scope.log ENV SCOPE_METRIC_VERBOSITY=4 ENV SCOPE_EVENT_LOGFILE=true diff --git a/test/integration/go/test_go.sh b/test/integration/go/test_go.sh index e102661dc..f8c0d26cd 100755 --- a/test/integration/go/test_go.sh +++ b/test/integration/go/test_go.sh @@ -10,6 +10,8 @@ ERR_FILE="stderr.txt" LOG_FILE="/tmp/scope.log" touch $EVT_FILE +GO_MAJOR_VER=(`echo $GOLANG_VERSION | cut -d '.' -f2`) + starttest(){ CURRENT_TEST=$1 echo "===============================================" @@ -165,6 +167,57 @@ ERR+=$? endtest +# +# plainServerDynamicPie +# +starttest plainServerDynamicPie +cd /go/net +PORT=80 + +ldscope ./plainServerDynamicPie ${PORT} & + +# this sleep gives the server a chance to bind to the port +# before we try to hit it with curl +sleep 1 +curl http://localhost:${PORT}/hello +ERR+=$? + +sleep 0.5 +# This stops plainServerDynamicPie +pkill -f plainServerDynamicPie + +# this sleep gives plainServerDynamicPie a chance to report its events on exit +sleep 1 + +evaltest + +grep plainServerDynamicPie $EVT_FILE | grep net.app > /dev/null +ERR+=$? +grep plainServerDynamicPie $EVT_FILE | grep net.open > /dev/null +ERR+=$? +grep plainServerDynamicPie $EVT_FILE | grep net.close > /dev/null +ERR+=$? +grep plainServerDynamicPie $EVT_FILE | grep fs.open > /dev/null +ERR+=$? +grep plainServerDynamicPie $EVT_FILE | grep fs.close > /dev/null +ERR+=$? +grep plainServerDynamicPie $EVT_FILE | grep http.req > /dev/null +ERR+=$? +grep plainServerDynamicPie $EVT_FILE | grep http.resp > /dev/null +ERR+=$? +grep plainServerDynamicPie $EVT_FILE | grep http.resp | grep "127.0.0.1" > /dev/null +ERR+=$? + +if [ $ERR -ge 1 ]; then + cat $EVT_FILE +fi + +evalPayload +ERR+=$? + +endtest + + # # plainServerStatic # @@ -398,6 +451,53 @@ ERR+=$? endtest +# +# plainClientStaticStripped +# +starttest plainClientStaticStripped +cd /go/net +ldscope ./plainClientStaticStripped +ERR+=$? + +# this sleep gives plainClientStaticStripped a chance to report its events on exit +sleep 1 + +evaltest + +# We don't support Go stripped executables < 1.13 +if [ $GO_MAJOR_VER -lt 13 ]; then + grep "Continuing without AppScope." ${LOG_FILE} + ERR+=$? +else + grep plainClientStaticStripped $EVT_FILE | grep net.app > /dev/null + ERR+=$? + grep plainClientStaticStripped $EVT_FILE | grep net.open > /dev/null + ERR+=$? + # dont wait for this, it's not always guaranteed in the test app's timeframe + #grep plainClientStaticStripped $EVT_FILE | grep net.close > /dev/null + #ERR+=$? + grep plainClientStaticStripped $EVT_FILE | grep fs.open > /dev/null + ERR+=$? + grep plainClientStaticStripped $EVT_FILE | grep fs.close > /dev/null + ERR+=$? + grep plainClientStaticStripped $EVT_FILE | grep http.req > /dev/null + ERR+=$? + grep plainClientStaticStripped $EVT_FILE | grep http.resp > /dev/null + ERR+=$? + grep plainClientStaticStripped $EVT_FILE | grep console > /dev/null + ERR+=$? +fi + +if [ $ERR -ge 1 ]; then + cat $EVT_FILE +fi + +evalPayload +ERR+=$? + +endtest + + # # tlsClientDynamic # diff --git a/test/integration/terraform/Dockerfile b/test/integration/terraform/Dockerfile new file mode 100644 index 000000000..62fae7ab3 --- /dev/null +++ b/test/integration/terraform/Dockerfile @@ -0,0 +1,37 @@ +FROM hashicorp/terraform:1.1.9 + +RUN apk add bash + +RUN mkdir -p /opt/terraform +COPY ./terraform/main.tf /opt/terraform/main.tf + +ENV SCOPE_CRIBL_ENABLE=false +ENV SCOPE_LOG_LEVEL=debug +ENV SCOPE_METRIC_VERBOSITY=4 +ENV SCOPE_EVENT_LOGFILE=true +ENV SCOPE_EVENT_CONSOLE=true +ENV SCOPE_EVENT_METRIC=true +ENV SCOPE_EVENT_HTTP=true +ENV SCOPE_EVENT_NET=true +ENV SCOPE_EVENT_FS=true +ENV SCOPE_LOG_DEST=file:///opt/test-runner/logs/scope.log +ENV SCOPE_EVENT_DEST=file:///opt/test-runner/logs/events.log +ENV SCOPE_METRIC_DEST=file:///opt/test-runner/logs/metrics.log + +ENV PATH="/usr/local/scope:/usr/local/scope/bin:${PATH}" +COPY scope-profile.sh /etc/profile.d/scope.sh +COPY gdbinit /root/.gdbinit + +RUN mkdir /usr/local/scope && \ + mkdir /usr/local/scope/bin && \ + mkdir /usr/local/scope/lib && \ + mkdir -p /opt/test-runner/logs/ && \ + ln -s /opt/appscope/bin/linux/$(uname -m)/scope /usr/local/scope/bin/scope && \ + ln -s /opt/appscope/bin/linux/$(uname -m)/ldscope /usr/local/scope/bin/ldscope && \ + ln -s /opt/appscope/lib/linux/$(uname -m)/libscope.so /usr/local/scope/lib/libscope.so + +COPY terraform/scope-test /usr/local/scope/scope-test + +COPY docker-entrypoint.sh / +ENTRYPOINT ["/docker-entrypoint.sh"] +CMD ["test"] diff --git a/test/integration/terraform/main.tf b/test/integration/terraform/main.tf new file mode 100644 index 000000000..c598b2ace --- /dev/null +++ b/test/integration/terraform/main.tf @@ -0,0 +1,8 @@ +terraform { + required_version = ">= 0.12.26" +} + +# The simplest possible Terraform module: it just outputs "Hello, World!" +output "hello_world" { + value = "Hello, World!" +} diff --git a/test/integration/terraform/scope-test b/test/integration/terraform/scope-test new file mode 100755 index 000000000..d36c39aac --- /dev/null +++ b/test/integration/terraform/scope-test @@ -0,0 +1,106 @@ +#!/bin/bash + +DEBUG=0 # set this to 1 to capture the EVT_FILE for each test + +FAILED_TEST_LIST="" +FAILED_TEST_COUNT=0 +LOOP_LIMIT=10 +EVT_FILE="/opt/test-runner/logs/events.log" +METRIC_FILE="/opt/test-runner/logs/metrics.log" +SCOPE_LOG_FILE="/opt/test-runner/logs/scope.log" + +starttest(){ + CURRENT_TEST=$1 + echo "===============================================" + echo " Testing $CURRENT_TEST " + echo "===============================================" + ERR=0 +} + +evaltest(){ + echo " Evaluating $CURRENT_TEST" +} + +endtest(){ + if [ $ERR -eq "0" ]; then + RESULT=PASSED + else + RESULT=FAILED + FAILED_TEST_LIST+=$CURRENT_TEST + FAILED_TEST_LIST+=" " + FAILED_TEST_COUNT=$(($FAILED_TEST_COUNT + 1)) + fi + + echo "*************** $CURRENT_TEST $RESULT ***************" + echo "" + echo "" + + # copy the EVT_FILE/METRIC_FILE/SCOPE_LOG_FILE to help with debugging + if (( $DEBUG )) || [ $RESULT == "FAILED" ]; then + cp -f $EVT_FILE $EVT_FILE.$CURRENT_TEST + cp -f $METRIC_FILE $METRIC_FILE.$CURRENT_TEST + cp -f $SCOPE_LOG_FILE $SCOPE_LOG_FILE.$CURRENT_TEST + fi + + rm -f $METRIC_FILE + rm -f $EVT_FILE + rm -f $SCOPE_LOG_FILE +} + +# +# test terraform +# + +starttest terraform + +cd /opt/terraform + +for i in $(seq $LOOP_LIMIT); do + ldscope -- /bin/terraform init + if [ $? -ne 0 ]; then + ERR+=1 + fi + grep "/bin/terraform init" $EVT_FILE | grep "fs.open" > /dev/null + if [ $? -ne 0 ]; then + ERR+=1 + fi + + ldscope -- /bin/terraform apply -auto-approve + if [ $? -ne 0 ]; then + ERR+=1 + fi + + grep "/bin/terraform apply" $EVT_FILE | grep "fs.open" > /dev/null + if [ $? -ne 0 ]; then + ERR+=1 + fi + + ldscope -- /bin/terraform destroy -auto-approve + if [ $? -ne 0 ]; then + ERR+=1 + fi + + grep "/bin/terraform destroy" $EVT_FILE | grep "fs.open" > /dev/null + if [ $? -ne 0 ]; then + ERR+=1 + fi + + rm -f $EVT_FILE +done + +endtest + +if (( $FAILED_TEST_COUNT == 0 )); then + echo "" + echo "" + echo "*************** ALL TESTS PASSED ***************" +else + echo "*************** SOME TESTS FAILED ***************" + echo "Failed tests: $FAILED_TEST_LIST" + echo "Refer to these files for more info:" + for FAILED_TEST in $FAILED_TEST_LIST; do + echo " $EVT_FILE.$FAILED_TEST" + done +fi + +exit ${FAILED_TEST_COUNT}