From: Mahesh Salgaonkar <mahesh@xxxxxxxxxxxxxxxxxx> This patch enables to makedumpfile to read and process filter commands from specified config file. It builds a list of filter info consisting of memory address (paddr) and size to be filtered out. This list is used during read_pfn() function to filter out user specified kernel data from vmcore before writing it to compressed DUMPFILE. The filtered memory locations are filled with 'X' (0x58) character. The filter command syntax is: erase <Symbol>[.member[...]] [size <SizeValue>[K|M]] erase <Symbol>[.member[...]] [size <SizeSymbol>] erase <Symbol>[.member[...]] [nullify] Below are the examples how filter commands in config file look like: erase modules erase cred_jar.name size 10 erase vmlist.addr nullify This is useful feature for the customers who wants to erase the customer sensitive data like security keys, in DUMPFILE before sending it to support team for analysis. The man page for makedumpfile.conf(8) has been introduced that describes the config file format and erase command syntax. Change in v2: - Renamed filter.conf file to makedumpfile.conf - Added cleanup routine clear_filter_info() to freeup memory allocated during gather_filter_info(). - Modified gather_filter_info() function to return error if process_config_file() can't open configuration file. Signed-off-by: Mahesh Salgaonkar <mahesh at linux.vnet.ibm.com> Signed-off-by: Prerna Saxena <prerna at linux.vnet.ibm.com> --- Makefile | 8 makedumpfile.c | 863 +++++++++++++++++++++++++++++++++++++++++++++++++++ makedumpfile.conf | 82 +++++ makedumpfile.conf.8 | 213 +++++++++++++ makedumpfile.h | 60 ++++ 5 files changed, 1225 insertions(+), 1 deletions(-) create mode 100644 makedumpfile.conf create mode 100644 makedumpfile.conf.8 diff --git a/Makefile b/Makefile index 47467e3..240b0f4 100644 --- a/Makefile +++ b/Makefile @@ -37,12 +37,18 @@ makedumpfile: $(SRC) $(OBJ_ARCH) grep -v "^.TH MAKEDUMPFILE 8" makedumpfile.8 >> temp.8 mv temp.8 makedumpfile.8 gzip -c ./makedumpfile.8 > ./makedumpfile.8.gz + echo .TH FILTER.CONF 8 \"$(DATE)\" \"makedumpfile v$(VERSION)\" \"Linux System Administrator\'s Manual\" > temp.8 + grep -v "^.TH FILTER.CONF 8" makedumpfile.conf.8 >> temp.8 + mv temp.8 makedumpfile.conf.8 + gzip -c ./makedumpfile.conf.8 > ./makedumpfile.conf.8.gz clean: - rm -f $(OBJ) $(OBJ_ARCH) makedumpfile makedumpfile.8.gz + rm -f $(OBJ) $(OBJ_ARCH) makedumpfile makedumpfile.8.gz makedumpfile.conf.8.gz install: cp makedumpfile ${DESTDIR}/bin cp makedumpfile-R.pl ${DESTDIR}/bin cp makedumpfile.8.gz ${DESTDIR}/usr/share/man/man8 + cp makedumpfile.conf.8.gz ${DESTDIR}/usr/share/man/man8 + cp makedumpfile.conf ${DESTDIR}/etc/makedumpfile.conf.sample diff --git a/makedumpfile.c b/makedumpfile.c index f7d37cc..1c7237a 100644 --- a/makedumpfile.c +++ b/makedumpfile.c @@ -27,10 +27,13 @@ struct dwarf_info dwarf_info; struct vm_table vt = { 0 }; struct DumpInfo *info = NULL; struct module_sym_table mod_st = { 0 }; +struct filter_info *filter_info = NULL; +struct filter_config filter_config; char filename_stdout[] = FILENAME_STDOUT; int message_level; long pointer_size; +char config_buf[BUFSIZE_FGETS]; /* * Forward declarations @@ -6047,6 +6050,78 @@ get_num_dumpable(void) return num_dumpable; } +void +split_filter_info(struct filter_info *prev, unsigned long long next_paddr, + size_t size) +{ + struct filter_info *new; + + if ((new = calloc(1, sizeof(struct filter_info))) == NULL) { + ERRMSG("Can't allocate memory to split filter info\n"); + return; + } + new->nullify = prev->nullify; + new->paddr = next_paddr; + new->size = size; + new->next = prev->next; + prev->next = new; +} + +int +extract_filter_info(unsigned long long start_paddr, + unsigned long long end_paddr, + struct filter_info *fl_info) +{ + struct filter_info *fi = filter_info; + struct filter_info *prev = NULL; + size_t size1, size2; + + if (!fl_info) + return FALSE; + + while (fi) { + if ((fi->paddr >= start_paddr) && (fi->paddr < end_paddr)) { + size1 = end_paddr - fi->paddr; + if (fi->size <= size1) + break; + size2 = fi->size - size1; + fi->size = size1; + split_filter_info(fi, fi->paddr + size1, size2); + break; + } + prev = fi; + fi = fi->next; + } + if (fi) { + *fl_info = *fi; + fl_info->next = NULL; + /* Delete this node */ + if (!prev) + filter_info = fi->next; + else + prev->next = fi->next; + free(fi); + return TRUE; + } + return FALSE; +} + +void +filter_data_buffer(unsigned char *buf, unsigned long long paddr, + size_t size) +{ + struct filter_info fl_info; + unsigned char *buf_ptr; + + while (extract_filter_info(paddr, paddr + size, &fl_info)) { + buf_ptr = buf + (fl_info.paddr - paddr); + if (fl_info.nullify) + memset(buf_ptr, 0, fl_info.size); + else + memset(buf_ptr, 'X', fl_info.size); + } +} + int write_elf_load_segment(struct cache_data *cd_page, unsigned long long paddr, off_t off_memory, long long size) @@ -6078,6 +6153,8 @@ write_elf_load_segment(struct cache_data *cd_page, unsigned long long paddr, info->name_memory, strerror(errno)); return FALSE; } + filter_data_buffer((unsigned char *)buf, paddr, bufsz_write); + paddr += bufsz_write; if (!write_cache(cd_page, buf, bufsz_write)) return FALSE; @@ -6296,6 +6373,7 @@ read_pfn(unsigned long long pfn, unsigned char *buf) ERRMSG("Can't get the page data.\n"); return FALSE; } + filter_data_buffer(buf, paddr, info->page_size); return TRUE; } @@ -6318,6 +6396,7 @@ read_pfn(unsigned long long pfn, unsigned char *buf) ERRMSG("Can't get the page data.\n"); return FALSE; } + filter_data_buffer(buf, paddr, size1); if (size1 != info->page_size) { size2 = info->page_size - size1; if (!offset2) { @@ -6327,6 +6406,7 @@ read_pfn(unsigned long long pfn, unsigned char *buf) ERRMSG("Can't get the page data.\n"); return FALSE; } + filter_data_buffer(buf + size1, paddr + size1, size2); } } return TRUE; @@ -7689,6 +7769,785 @@ load_module_symbols(void) return TRUE; } +void +free_config_entry(struct config_entry *ce) +{ + struct config_entry *p; + + while(ce) { + p = ce; + ce = p->next; + if (p->name) + free(p->name); + if (p->type_name) + free(p->type_name); + free(p); + } +} + +void +free_config(struct config *config) +{ + if (config) { + if (config->module_name) + free(config->module_name); + if (config->filter_symbol) + free_config_entry(config->filter_symbol); + if (config->size_symbol) + free_config_entry(config->size_symbol); + free(config); + } +} + +void +print_config_entry(struct config_entry *ce) +{ + while (ce) { + DEBUG_MSG("Name: %s\n", ce->name); + DEBUG_MSG("Type Name: %s, ", ce->type_name); + DEBUG_MSG("flag: %x, ", ce->flag); + DEBUG_MSG("Type flag: %lx, ", ce->type_flag); + DEBUG_MSG("sym_addr: %llx, ", ce->sym_addr); + DEBUG_MSG("addr: %llx, ", ce->addr); + DEBUG_MSG("offset: %lx, ", ce->offset); + DEBUG_MSG("size: %zd\n", ce->size); + + ce = ce->next; + } +} + +/* + * Read the non-terminal's which are in the form of <Symbol>[.member[...]] + */ +struct config_entry * +create_config_entry(const char *token, unsigned short flag, int line) +{ + struct config_entry *ce = NULL, *ptr, *prev_ce; + char *str, *cur, *next; + long len; + int depth = 0; + + if (!token) + return NULL; + + cur = str = strdup(token); + prev_ce = ptr = NULL; + while (cur != NULL) { + if ((next = strchr(cur, '.')) != NULL) { + *next++ = '\0'; + } + if (!strlen(cur)) { + cur = next; + continue; + } + + if ((ptr = calloc(1, sizeof(struct config_entry))) == NULL) { + ERRMSG("Can't allocate memory for config_entry\n"); + goto err_out; + } + ptr->line = line; + ptr->flag |= flag; + if (depth == 0) { + /* First node is always a symbol name */ + ptr->flag |= SYMBOL_ENTRY; + } + if (flag & FILTER_ENTRY) { + ptr->name = strdup(cur); + } + if (flag & SIZE_ENTRY) { + char ch = '\0'; + int n = 0; + /* See if absolute length is provided */ + if ((depth == 0) && + ((n = sscanf(cur, "%zd%c", &len, &ch)) > 0)) { + if (len < 0) { + ERRMSG("Config error at %d: size " + "value must be positive.\n", + line); + goto err_out; + } + ptr->size = len; + ptr->flag |= ENTRY_RESOLVED; + if (n == 2) { + /* Handle suffix. + * K = Kilobytes + * M = Megabytes + */ + switch (ch) { + case 'M': + case 'm': + ptr->size *= 1024; + case 'K': + case 'k': + ptr->size *= 1024; + break; + } + } + } + else + ptr->name = strdup(cur); + } + if (prev_ce) { + prev_ce->next = ptr; + prev_ce = ptr; + } + else + ce = prev_ce = ptr; + cur = next; + depth++; + ptr = NULL; + } + free(str); + return ce; + +err_out: + if (ce) + free_config_entry(ce); + if (ptr) + free_config_entry(ptr); + return NULL; +} + +int +is_module_loaded(char *mod_name) +{ + if (!strcmp(mod_name, "vmlinux") || get_loaded_module(mod_name)) + return TRUE; + return FALSE; +} + +/* + * read filter config file and return each string token. If the parameter + * expected_token is non-NULL, then return the current token if it matches + * with expected_token otherwise save the current token and return NULL. + * At start of every module section filter_config.new_section is set to 1 and + * subsequent function invocations return NULL untill filter_config.new_section + * is reset to 0 by passing @flag = CONFIG_NEW_CMD (0x02). + * + * Parameters: + * @expected_token INPUT + * Token string to match with currnet token. + * =NULL - return the current available token. + * + * @flag INPUT + * =0x01 - Skip to next module section. + * =0x02 - Treat the next token as next filter command and reset. + * + * @line OUTPUT + * Line number of current token in filter config file. + * + * @cur_mod OUTPUT + * Points to current module section name on non-NULL return value. + * + * @eof OUTPUT + * set to -1 when end of file is reached. + * set to -2 when end of section is reached. + */ +static char * +get_config_token(char *expected_token, unsigned char flag, int *line, + char **cur_mod, int *eof) +{ + char *p; + struct filter_config *fc = &filter_config; + int skip = flag & CONFIG_SKIP_SECTION; + + if (!fc->file_filterconfig) + return NULL; + + if (eof) + *eof = 0; + + /* + * set token and saved_token to NULL if skip module section is set + * to 1. + */ + if (skip) { + fc->token = NULL; + fc->saved_token = NULL; + } + + if (fc->saved_token) { + fc->token = fc->saved_token; + fc->saved_token = NULL; + } + else if (fc->token) + fc->token = strtok(NULL, " "); + + /* Read next line if we are done all tokens from previous line */ + while (!fc->token && fgets(config_buf, sizeof(config_buf), + fc->file_filterconfig)) { + if ((p = strchr(config_buf, '\n'))) { + *p = '\0'; + fc->line_count++; + } + if ((p = strchr(config_buf, '#'))) { + *p = '\0'; + } + /* replace all tabs with spaces */ + for (p = config_buf; *p != '\0'; p++) + if (*p == '\t') + *p = ' '; + if (config_buf[0] == '[') { + /* module section entry */ + p = strchr(config_buf, ']'); + if (!p) { + ERRMSG("Config error at %d: Invalid module " + "section entry.\n", fc->line_count); + /* skip to next valid module section */ + skip = 1; + } + else { + /* + * Found the valid module section. Reset the + * skip flag. + */ + *p = '\0'; + if (fc->cur_module) + free(fc->cur_module); + fc->cur_module = strdup(&config_buf[1]); + skip = 0; + fc->new_section = 1; + } + continue; + } + /* + * If symbol info for current module is not loaded then + * skip to next module section. + */ + if (skip || + (fc->cur_module && !is_module_loaded(fc->cur_module))) + continue; + + fc->token = strtok(config_buf, " "); + } + if (!fc->token) { + if (eof) + *eof = -1; + return NULL; + } + if (fc->new_section && !(flag & CONFIG_NEW_CMD)) { + fc->saved_token = fc->token; + if (eof) + *eof = -2; + return NULL; + } + else + fc->new_section = 0; + + if (cur_mod) + *cur_mod = fc->cur_module; + + if (line) + *line = fc->line_count; + + if (expected_token && strcmp(fc->token, expected_token)) { + fc->saved_token = fc->token; + return NULL; + } + return fc->token; +} + +static int +read_size_entry(struct config *config, int line) +{ + char *token = get_config_token(NULL, 0, &line, NULL, NULL); + + if (!token || IS_KEYWORD(token)) { + ERRMSG("Config error at %d: expected size symbol after" + " 'size' keyword.\n", line); + return FALSE; + } + config->size_symbol = create_config_entry(token, SIZE_ENTRY, line); + if (!config->size_symbol) { + ERRMSG("Error at line %d: Failed to read size symbol\n", + line); + return FALSE; + } + return TRUE; +} + +/* + * Read erase command entry. The erase command syntax is: + * + * erase <Symbol>[.member[...]] [size <SizeValue>[K|M]] + * erase <Symbol>[.member[...]] [size <SizeSymbol>] + * erase <Symbol>[.member[...]] [nullify] + */ +static int +read_filter_entry(struct config *config, int line) +{ + char *token = get_config_token(NULL, 0, &line, NULL, NULL); + + if (!token || IS_KEYWORD(token)) { + ERRMSG("Config error at %d: expected kernel symbol after" + " 'erase' command.\n", line); + return FALSE; + } + config->filter_symbol = + create_config_entry(token, FILTER_ENTRY, line); + if (!config->filter_symbol) { + ERRMSG("Error at line %d: Failed to read filter symbol\n", + line); + return FALSE; + } + if (get_config_token("nullify", 0, &line, NULL, NULL)) { + config->filter_symbol->nullify = 1; + } + else if (get_config_token("size", 0, &line, NULL, NULL)) { + if (!read_size_entry(config, line)) + return FALSE; + } + return TRUE; +} + +/* + * Configuration file 'makedumpfile.conf' contains filter commands. + * Every individual filter command is considered as a config entry. A config + * entry can be provided on a single line or multiple lines. + */ +struct config * +get_config(int skip) +{ + struct config *config; + char *token = NULL; + static int line_count = 0; + char *cur_module = NULL; + int eof = 0; + unsigned char flag = CONFIG_NEW_CMD | skip; + + if ((config = calloc(1, sizeof(struct config))) == NULL) + return NULL; + + if (get_config_token("erase", flag, &line_count, &cur_module, &eof)) { + if (cur_module) + config->module_name = strdup(cur_module); + + if (!read_filter_entry(config, line_count)) + goto err_out; + } + else { + if (!eof) { + token = get_config_token(NULL, 0, &line_count, + NULL, NULL); + ERRMSG("Config error at %d: Invalid token '%s'.\n", + line_count, token); + } + goto err_out; + } + return config; +err_out: + if (config) + free_config(config); + return NULL; +} + +static unsigned long long +read_pointer_value(unsigned long long addr) +{ + uint32_t val_32; + uint64_t val_64; + + switch (pointer_size) { + case 4: + if (!readmem(VADDR, addr, &val_32, sizeof(val_32))) { + ERRMSG("Can't read pointer value\n"); + return 0; + } + return (unsigned long long)val_32; + break; + case 8: + if (!readmem(VADDR, addr, &val_64, sizeof(val_64))) { + ERRMSG("Can't read pointer value\n"); + return 0; + } + return (unsigned long long)val_64; + break; + } + return 0; +} + +int +resolve_config_entry(struct config_entry *ce, unsigned long long base_addr, + char *base_struct_name) +{ + char buf[BUFSIZE + 1]; + + if (ce->flag & SYMBOL_ENTRY) { + /* find the symbol info */ + if (!ce->name) + return FALSE; + + ce->sym_addr = get_symbol_addr(ce->name); + if (!ce->sym_addr) { + ERRMSG("Config error at %d: Can't find symbol '%s'.\n", + ce->line, ce->name); + return FALSE; + } + ce->type_name = get_symbol_type_name(ce->name, + DWARF_INFO_GET_SYMBOL_TYPE, + &ce->size, &ce->type_flag); + if (ce->type_flag & TYPE_ARRAY) { + ce->array_length = get_array_length(ce->name, NULL, + DWARF_INFO_GET_SYMBOL_ARRAY_LENGTH); + if (ce->array_length < 0) + ce->array_length = 0; + } + } + else { + /* find the member offset */ + ce->offset = get_member_offset(base_struct_name, + ce->name, DWARF_INFO_GET_MEMBER_OFFSET); + ce->sym_addr = base_addr + ce->offset; + ce->type_name = get_member_type_name(base_struct_name, + ce->name, DWARF_INFO_GET_MEMBER_TYPE, + &ce->size, &ce->type_flag); + if (ce->type_flag & TYPE_ARRAY) { + ce->array_length = get_array_length(base_struct_name, + ce->name, + DWARF_INFO_GET_MEMBER_ARRAY_LENGTH); + if (ce->array_length < 0) + ce->array_length = 0; + } + } + if (ce->type_name == NULL) { + if (!(ce->flag & SYMBOL_ENTRY)) + ERRMSG("Config error at %d: struct '%s' has no member" + " with name '%s'.\n", + ce->line, base_struct_name, ce->name); + return FALSE; + } + ce->addr = ce->sym_addr; + if (ce->size < 0) + ce->size = 0; + if ((ce->type_flag & (TYPE_ARRAY | TYPE_PTR)) == TYPE_PTR) { + /* If it's a pointer variable (not array) then read the + * pointer value. */ + ce->addr = read_pointer_value(ce->sym_addr); + + /* + * if it is a void pointer then reset the size to 0 + * User need to provide a size to filter data referenced + * by 'void *' pointer. + */ + if (!strcmp(ce->type_name, "void")) + ce->size = 0; + + } + if ((ce->type_flag & TYPE_BASE) && (ce->type_flag & TYPE_PTR)) { + /* + * Determine the string length for 'char' pointer. + * BUFSIZE(1024) is the upper limit for string length. + */ + if (!strcmp(ce->type_name, "char")) { + if (readmem(VADDR, ce->addr, buf, BUFSIZE)) { + buf[BUFSIZE] = '\0'; + ce->size = strlen(buf); + } + } + } + if (!ce->next && (ce->flag & SIZE_ENTRY)) { + void *val; + + /* leaf node of size entry */ + /* If it is size argument then update the size with data + * value of this symbol/member. + * Check if current symbol/member is of base data type. + */ + + if (((ce->type_flag & (TYPE_ARRAY | TYPE_BASE)) != TYPE_BASE) + || (ce->size > sizeof(long))) { + ERRMSG("Config error at %d: size symbol/member '%s' " + "is not of base type.\n", ce->line, ce->name); + return FALSE; + } + if ((val = calloc(1, ce->size)) == NULL) { + ERRMSG("Can't get memory for size parameter\n"); + return FALSE; + } + + if (!readmem(VADDR, ce->addr, val, ce->size)) { + ERRMSG("Can't read symbol/member data value\n"); + return FALSE; + } + switch (ce->size) { + case 1: + ce->size = (long)(*((uint8_t *)val)); + break; + case 2: + ce->size = (long)(*((uint16_t *)val)); + break; + case 4: + ce->size = (long)(*((uint32_t *)val)); + break; + case 8: + ce->size = (long)(*((uint64_t *)val)); + break; + } + free(val); + } + ce->flag |= ENTRY_RESOLVED; + return TRUE; +} + +unsigned long long +get_config_symbol_addr(struct config_entry *ce, + unsigned long long base_addr, + char *base_struct_name) +{ + if (!(ce->flag & ENTRY_RESOLVED)) { + if (!resolve_config_entry(ce, base_addr, base_struct_name)) + return 0; + } + + if (ce->next && ce->addr) { + /* Populate nullify flag down the list */ + ce->next->nullify = ce->nullify; + return get_config_symbol_addr(ce->next, ce->addr, + ce->type_name); + } + else if (ce->nullify) { + /* nullify is applicable to pointer type */ + if (ce->type_flag & TYPE_PTR) + return ce->sym_addr; + else + return 0; + } + else + return ce->addr; +} + +long +get_config_symbol_size(struct config_entry *ce, + unsigned long long base_addr, + char *base_struct_name) +{ + if (!(ce->flag & ENTRY_RESOLVED)) { + if (!resolve_config_entry(ce, base_addr, base_struct_name)) + return 0; + } + + if (ce->next && ce->addr) + return get_config_symbol_size(ce->next, ce->addr, + ce->type_name); + else { + if (ce->type_flag & TYPE_ARRAY) { + if (ce->type_flag & TYPE_PTR) + return ce->array_length * pointer_size; + else + return ce->array_length * ce->size; + } + return ce->size; + } +} + +/* + * Insert the filter info node using insertion sort. + * If filter node for a given paddr is aready present then update the size + * and delete the fl_info node passed. + */ +void +insert_filter_info(struct filter_info *fl_info) +{ + struct filter_info *prev = NULL; + struct filter_info *ptr = filter_info; + + if (!ptr) { + filter_info = fl_info; + return; + } + + while (ptr) { + if (fl_info->paddr <= ptr->paddr) + break; + prev = ptr; + ptr = ptr->next; + } + if (ptr && (fl_info->paddr == ptr->paddr)) { + if (fl_info->size > ptr->size) + ptr->size = fl_info->size; + free(fl_info); + return; + } + + if (prev) { + fl_info->next = ptr; + prev->next = fl_info; + } + else { + fl_info->next = filter_info; + filter_info = fl_info; + } + return; +} + +int +update_filter_info(struct config_entry *filter_symbol, + struct config_entry *size_symbol) +{ + unsigned long long addr; + long size; + struct filter_info *fl_info; + + addr = get_config_symbol_addr(filter_symbol, 0, NULL); + if (message_level & ML_PRINT_DEBUG_MSG) + print_config_entry(filter_symbol); + if (!addr) + return FALSE; + + if (filter_symbol->nullify) + size = pointer_size; + else if (size_symbol) { + size = get_config_symbol_size(size_symbol, 0, NULL); + if (message_level & ML_PRINT_DEBUG_MSG) + print_config_entry(size_symbol); + } + else + size = get_config_symbol_size(filter_symbol, 0, NULL); + + if (size <= 0) + return FALSE; + + if ((fl_info = calloc(1, sizeof(struct filter_info))) == NULL) { + ERRMSG("Can't allocate filter info\n"); + return FALSE; + } + fl_info->address = addr; + fl_info->paddr = vaddr_to_paddr(addr); + fl_info->size = size; + fl_info->nullify = filter_symbol->nullify; + + insert_filter_info(fl_info); + return TRUE; +} + +/* + * Process the config entry that has been read by get_config. + * return TRUE on success + */ +int +process_config(struct config *config) +{ + update_filter_info(config->filter_symbol, config->size_symbol); + + return TRUE; +} + +void +print_filter_info() +{ + struct filter_info *fl_info = filter_info; + + DEBUG_MSG("\n"); + while (fl_info) { + DEBUG_MSG("filter address: paddr (%llx), sym_addr (%llx)," + " Size (%ld)\n", + fl_info->paddr, fl_info->address, fl_info->size); + fl_info = fl_info->next; + } +} + +void +init_filter_config() +{ + filter_config.name_filterconfig = info->name_filterconfig; + filter_config.file_filterconfig = info->file_filterconfig; + filter_config.saved_token = NULL; + filter_config.token = NULL; + filter_config.cur_module = NULL; + filter_config.new_section = 0; + filter_config.line_count = 0; +} + +/* + * Read and process each config entry (filter commands) from filter config + * file. If no module debuginfo found for specified module section then skip + * to next module section. + */ +int +process_config_file(const char *name_config) +{ + struct config *config; + int skip_section = 0; + + if (!name_config) + return FALSE; + + if ((info->file_filterconfig = fopen(name_config, "r")) == NULL) { + ERRMSG("Can't open config file(%s). %s\n", + name_config, strerror(errno)); + return FALSE; + } + + init_filter_config(); + + while((config = get_config(skip_section)) != NULL) { + skip_section = 0; + if (config->module_name && + strcmp(config->module_name, "vmlinux")) { + /* + * if Module debuginfo is not available, then skip to + * next module section. + */ + if (!set_dwarf_debuginfo(config->module_name, + NULL, -1)) { + ERRMSG("Skipping to next Module section\n"); + skip_section = 1; + free_config(config); + continue; + } + } + else { + set_dwarf_debuginfo("vmlinux", info->name_vmlinux, + info->fd_vmlinux); + } + process_config(config); + free_config(config); + } + + fclose(info->file_filterconfig); + print_filter_info(); + return TRUE; +} + +int +gather_filter_info() +{ + int ret; + + /* + * Before processing filter config file, load the symbol data of + * loaded modules from vmcore. + */ + set_dwarf_debuginfo("vmlinux", info->name_vmlinux, info->fd_vmlinux); + if (!load_module_symbols()) + return FALSE; + + ret = process_config_file(info->name_filterconfig); + + /* + * Remove modules symbol information, we dont need now. + * Reset the dwarf debuginfo to vmlinux to close open file + * descripter of module debuginfo file, if any. + */ + clean_module_symbols(); + set_dwarf_debuginfo("vmlinux", info->name_vmlinux, info->fd_vmlinux); + return ret; +} + +void +clear_filter_info(void) +{ + struct filter_info *prev, *fi = filter_info; + + /* Delete filter_info nodes that are left out. */ + while (fi) { + prev = fi; + fi = fi->next; + free(prev); + } + filter_info = NULL; +} + int create_dumpfile(void) { @@ -7724,6 +8583,9 @@ retry: } } + if (info->name_filterconfig && !gather_filter_info()) + return FALSE; + if (!create_dump_bitmap()) return FALSE; @@ -7750,6 +8612,7 @@ retry: } print_report(); + clear_filter_info(); if (!close_files_for_creating_dumpfile()) return FALSE; diff --git a/makedumpfile.conf b/makedumpfile.conf new file mode 100644 index 0000000..3d47e25 --- /dev/null +++ b/makedumpfile.conf @@ -0,0 +1,82 @@ +## Filter config file +## +## Description: +## Configuration file to specify filter commands to filter out desired +## kernel data from vmcore. It supports erasing of symbol data and +## it's members of any data type. In case of filtering of data pointed by +## void * pointer, user needs to provide size to filter out. +## +## Please refer to manpage makedumpfile.conf(8) for more details. +## +## +## - Module section +## ========================================================= +## Syntax: +## [ModuleName] +## +## Define the module section where the symbols specified in subsequent erase +## commands belong to. The section name is a kernel module name (including +## vmlinux). The unnamed section defaults to [vmlinux] section. +## +## NOTE: There should not be any whitespaces before or after the ModuleName. +## +## e.g. +## [vmlinux] # Symbols in erase command belongs to main kernel (vmlinux) +## erase modules +## erase cred_jar.name +## erase cred_jar.name size 10 +## erase cred_jar.array +## erase vmlist.addr nullify +## +## [z90crypt] # Symbols in erase command belongs to kernel module z90crypt +## erase ap_device_list +## +## # erase entire CPRBX structure +## erase static_cprbx +## +## +## - To erase kernel data referred through a kernel Symbol +## ========================================================= +## Syntax: +## erase <Symbol>[.member[...]] [size <SizeValue>[K|M]] +## erase <Symbol>[.member[...]] [size <SizeSymbol>] +## erase <Symbol>[.member[...]] [nullify]] +## +## where +## <Symbol> +## A variable name from the kernel or module, which is part of +## global symbols '/proc/kallsyms'. +## <SizeValue> +## Integer value that specifies size of data to be erased. The +## suffixes 'K' and 'M' can be used to specify kilobytes and +## megabytes respectively where, K means 1024 bytes and M means +## 1024 ^ 2 = 1048576 bytes. +## <SizeSymbol> +## A simple axpression of the form <Symbol>[.member[...]] that +## denotes a symbol which contains a positive integer value as a +## size of the data in bytes to be erased. +## +## Filter out the specified size of the data referred by symbol/member. +## If size option is not provided then the size of <Symbol> will be calculated +## according to it's data type. For 'char *' data type, string length will be +## determined with an upper limit of 1024. +## +## If specified <Symbol> is of type 'void *', then user needs to provide +## either 'size' or 'nullify' option. Otherwise erase command will not have +## any effect. +## +## The option 'nullify' will work only if filter symbol/member is a pointer and +## is used to set NULL value to the pointer type symbol/member. +## +## NOTE: Please note that by nullifying pointer values will affect the +## debug ability of created DUMPFILE. Use 'nullify' option only when size of +## data to be filter out is not known e.g. data pointed by 'void *'. +## +## e.g. +## [vmlinux] +## erase modules +## erase cred_jar.name +## erase cred_jar.name size 10 +## erase cred_jar.array +## erase vmlist.addr nullify +## diff --git a/makedumpfile.conf.8 b/makedumpfile.conf.8 new file mode 100644 index 0000000..0a7d22a --- /dev/null +++ b/makedumpfile.conf.8 @@ -0,0 +1,213 @@ +.TH FILTER.CONF 8 "16 November 2010" "makedumpfile v1.3.7" "Linux System Administrator's Manual" +.SH NAME +makedumpfile.conf \- The filter configuration file for makedumpfile(8). +.SH DESCRIPTION +.PP +The makedumpfile.conf is a configuration file for makedumpfile tool. +makedumpfile.conf file contains the erase commands to filter out desired kernel +data from the vmcore while creating \fIDUMPFILE\fR using makedumpfile tool. +makedumpfile reads the filter config and builds the list of memory addresses +and its sizes after processing filter commands. The memory locations that +require to be filtered out are then poisoned with character ?X? (58 in Hex). +.SH FILE FORMAT +.PP +The file consists of module sections that contains filter commands. A section +begins with the name of the section in square brackets and continues until the +next section begins. + +.br +"["<\fIModuleName\fR>"]" +.br +<\fIFilterCommands\fR> +.br + +where +.br +"[" is the character \fB[\fR +.br +"]" is the character \fB]\fR +.TP +<\fIModuleName\fR> +is either 'vmlinux' or name of a Linux kernel module. +.TP +<\fIFilterCommands\fR> +is a list of one or more filter commands as described in the section +\fBFILTER COMMANDS\fR of this manual page. +.PP +The section name indicates a kernel module name (including \fBvmlinux\fR) where +the symbols specified in subsequent erase commands belong to. The unnamed +section defaults to \fB[vmlinux]\fR section. However, a user can also explicitly +define \fB[vmlinux]\fR section. The sections help makedumpfile tool to select +appropriate kernel or module debuginfo file before processing the subsequent +erase commands. Before selecting appropriate debuginfo file, the module name +is validated against the loaded modules from the vmcore. If no match is found, +then the section is ignored and makedumpfile skips to the next module section. +If match is found, then makedumpfile will try to load the corresponding +module debuginfo file. If module debuginfo is not available then, makedumpfile +will skip the section with a warning message. +.SH FILTER COMMANDS +.SS filter command +.PP +A filter command is an erase command. Each erase command must start with a new +line. Each filter command describes data in the dump to be erased. + +.br +<\fIEraseCommands\fR> +.br + +where +.TP +<\fIEraseCommands\fR> +Described in the subsection \fBerase command\fR of this manual page. +.SS erase command +.PP +Erase specified size of a kernel data referred by specified kernel/module +symbol or its member component. The erase command syntax is: + +.br +\fBerase\fR <\fISymbol\fR>[.\fImember\fR[...]] [\fBsize\fR +<\fISizeValue\fR>[K|M]] +.br +\fBerase\fR <\fISymbol\fR>[.\fImember\fR[...]] [\fBsize\fR <\fISizeSymbol\fR>] +.br +\fBerase\fR <\fISymbol\fR>[.\fImember\fR[...]] [\fBnullify\fR] +.br + +where +.br +.TP +<\fISymbol\fR> +A kernel or module symbol (variable) name that is part of global symbols +\fB/proc/kallsyms\fR. +.TP +<\fISizeValue\fR> +A positive integer value as a size of the data in bytes to be erased. The +suffixes 'K' and 'M' can be used to specify kilobytes and Megabytes +respectively where, K means 1024 bytes and M means 1024 ^ 2 = 1048576 bytes. +The suffixes are not case sensitive. +.TP +<\fISizeSymbol\fR> +A simple expression of the form <\fISymbol\fR>[.\fImember\fR[...]] that denotes +a symbol which contains a positive integer value as a size of the data in bytes +to be erased. +.TP +<\fISymbol\fR>[.\fImember\fR[...]] +A simple expression that results into either a global kernel symbol name or +its member components. The expression always uses '.' operator to specify +the \fImember\fR component of kernel symbol or its member irrespective of +whether it is of pointer type or not. +.TP +\fImember\fR[...] +Member or component of member in <\fISymbol\fR>. +.PP +The \fBerase\fR command takes two arguments 1. kernel symbol name or its +member components and 2. size of the data referred by argument (1) OR +\fBnullify\fR keyword. The second argument \fBsize\fR OR \fBnullify\fR is +optional. The unit for size value is in \fBbytes\fR. If \fBsize\fR option is +not specified then the size of the first argument is determined according to +its data type using dwarf info from debuginfo file. In case of '\fBchar *\fR' +data type, the length of string pointed by '\fBchar *\fR' pointer is determined +with an upper limit of 1024. The \fBsize\fR can be specified in two forms 1. +a integer value as explained above (<\fISizeValue\fR>) and 2. a simple +expression in the form of <\fISymbol\fR>[.\fImember\fR[...]]] that results into +base type (integer) variable. +.PP +If the specified <\fISymbol\fR> is of type '\fBvoid *\fR', then user needs to +provide either \fBsize\fR or \fBnullify\fR option, otherwise the erase command +will not have any effect. +.PP +The \fBnullify\fR option only works if specified <\fISymbol\fR> is a pointer. +Instead of erasing data pointed by the specified pointer \fBnullify\fR erases +the pointer value and set it to '0' (NULL). Please note that by nullifying +the pointer values may affect the debug ability of created \fIDUMPFILE\fR. +Use the \fBnullify\fR option only when the size of data to be erased is not +known. \fBe.g.\fR data pointed by '\fBvoid *\fR'. +.PP +Let us look at the makedumpfile.conf file from the example below which was +configured to erase desired kernel data from the kernel module with name +\fBmymodule\fR. At line 1 and 3, the user has not specified size option while +erasing 'array_var' and 'mystruct1.name' symbols. Instead the user depends on +makedumpfile to automatically determine the sizes to be erased \fBi.e\fR +100 bytes for 'array_var' and 11 bytes for 'mystruct1.name'. At line 2, +while erasing the 'mystruct1.buffer' member the user has specified the size +value 25 against the actual size of 50. In this case the user specified +\fBsize\fR takes the precedence and makedumpfile erases only 25 bytes from +\'mystruct1.buffer'. At line 4, the size of the data pointed by \fBvoid *\fR +pointer 'mystruct1.addr' is unknown. Hence the \fBnullify\fR option has been +specified to reset the pointer value to NULL. At line 5, the +\'mystruct2.addr_size' is specified as \fBsize\fR argument to determine the +size of the data pointed by \fBvoid *\fR pointer 'mystruct2.addr'. +.br + +.B Example: +.PP +Assuming the following piece of code is from kernel module 'mymodule': +.br + +struct s1 { +.br + char *name; +.br + void *addr1; +.br + void *addr2; +.br + char buffer[50]; +.br +}; +.br +struct s2 { +.br + void *addr; +.br + long addr_size; +.br +}; +.br + +/* Global symbols */ +.br +char array_var[100]; +.br +struct s1 mystruct1; +.br +struct s2 *mystruct2; +.br + +int foo() +.br +{ +.br + ... +.br + s1.name = "Hello World"; +.br + ... +.br +} +.br + +\fBmakedumpfile.conf:\fR +.br +[mymodule] +.br +erase array_var +.br +erase mystruct1.buffer size 25 +.br +erase mystruct1.name +.br +erase mystruct1.addr1 nullify +.br +# Assuming addr2 points to 1024 bytes +.br +erase mystruct1.addr2 size 1K +.br +erase mystruct2.addr size mystruct2.addr_size +.br +.B EOF + +.SH SEE ALSO +.PP +makedumpfile(8) + diff --git a/makedumpfile.h b/makedumpfile.h index 62c7ff5..94f3521 100644 --- a/makedumpfile.h +++ b/makedumpfile.h @@ -226,6 +226,7 @@ do { \ #define BITPERBYTE (8) #define PGMM_CACHED (512) #define PFN_EXCLUDED (256) +#define BUFSIZE (1024) #define BUFSIZE_FGETS (1500) #define BUFSIZE_BITMAP (4096) #define PFN_BUFBITMAP (BITPERBYTE*BUFSIZE_BITMAP) @@ -1277,6 +1278,65 @@ struct dwarf_info { extern struct dwarf_info dwarf_info; +/* + * Filtering information + */ +struct filter_info { + unsigned long long address; + unsigned long long paddr; + long size; + struct filter_info *next; + unsigned short nullify; +}; + +struct config_entry { + char *name; + char *type_name; + unsigned short flag; + unsigned short nullify; + unsigned long long sym_addr; /* Symbol address */ + unsigned long long addr; /* Symbol address or + value pointed by sym_addr */ + unsigned long offset; + unsigned long type_flag; + long array_length; + long size; + int line; /* Line number in config file. */ + struct config_entry *next; +}; + +/* flags for config_entry.flag */ +#define FILTER_ENTRY 0x0001 +#define SIZE_ENTRY 0x0002 +#define SYMBOL_ENTRY 0x0010 +#define ENTRY_RESOLVED 0x8000 + +/* + * Filter config information + */ +struct filter_config { + char *name_filterconfig; + FILE *file_filterconfig; + char *cur_module; + char *saved_token; + char *token; + int new_section; + int line_count; +}; + +#define CONFIG_SKIP_SECTION 0x01 +#define CONFIG_NEW_CMD 0x02 + +struct config { + char *module_name; + struct config_entry *filter_symbol; + struct config_entry *size_symbol; +}; + +#define IS_KEYWORD(tkn) \ + (!strcmp(tkn, "erase") || !strcmp(tkn, "size") || \ + !strcmp(tkn, "nullify")) + int readmem(int type_addr, unsigned long long addr, void *bufptr, size_t size); off_t paddr_to_offset(unsigned long long paddr); unsigned long long vaddr_to_paddr_general(unsigned long long vaddr);