kmodpatch/Makefile000644 000423 000000 00000000204 11127221270 014715 0ustar00luigiwheel000000 000000 # Makefile for kmodpatch CFLAGS+= -O1 -Wall -Werror -Wunused -g CFLAGS+= -I/usr/local/include -L/usr/local/lib -lkvm all: kmodpatch kmodpatch/kmodpatch.c000644 000423 000000 00000040664 11127217046 015417 0ustar00luigiwheel000000 000000 /* * $Id: kmodpatch.c 992 2009-01-01 12:54:01Z luigi $ * * Copyright (C) 2009 Luigi Rizzo * */ /* * DESCRIPTION Several USB, PCI and other device drivers require the kernel to have the exact product/vendor IDs (and sometimes matching quirks) in its internal tables, to identify the device and possibly enable/disable specific features. As an example, this is the case for many "umass" devices, where implementation of standard features varies, and for uscanner devices, where there is no specific device class info so the driver has to do an exact match using a lookup table. Apart from rebuilding the kernel or module, the device table can be altered with external tools, either on-disk or in memory. This program (kmodpatch) can be used to patch the in-memory kernel structures used to store the match/quirks tables, as follows: + use kldfind and kldsym to locate the module, address and size for the desired data structure; + use kvm_open/kvm_read/kvm_write to read and possibly update the table. Patching a live kernel is clearly dangerous, so we try to make the process a little less risky by putting some additional controls in this program. In particular, kmodpatch constains a table of the form module symbol record_format... defining which modules/symbols can be patched and the structure of each entry. As an example: uscanner.ko uscanner_devs 2:vendor 2:product 4:flags umass.ko umass_devdescrs 4 4 4 2 2 if_sis.ko sis_devs 2 2 s means that + module uscanner.ko has a device table called uscanner_devs where each record is made of 3 numeric fields of size 2 2 4 bytes, whose "names" (used to print the content of the table) are "vendor", "product" and "flags" respectively; + module umass.ko has a device table called umass_devdescr with records of size 4 4 4 2 2 bytes; + module if_sis.ko has a device table called sis_devs where each record has two numeric entries of 2 bytes each, plus a string pointer; + the kernel has a table called linker_file_sysinit with two numeric entries of size 2 bytes each, plus a string pointer. Additional format specifiers can be used for little or big-endian numbers, generic pointers and so on. Furthermore, comments can be added to the fields kmodpatch accepts commands of the form kldpatch if_sis.ko sis_devs write @3 0x1234 0x5678 "temporary entry" which means 'patch record 3 with the values specified". The frontend will make sure that operands are of the required size, that the table entry is within the table, and that (in case of patching constant strings) the replacement does not exceed the original length of the string. If a device is already connected before loading the module, the patching must occur before the module is given control. One way to achieve this is to hook the code that does the pacthing right before kern/kern_linker.c:: linker_file_sysinit() which is static. The closest non-static function call is linker_file_lookup_set(lf, "sysinit_set", &start, &stop, NULL) which has an almost-unique "sysinit_set argument. The action would be run the patch on all existing modules. Presumably we can just build a module that conditionally overwrites the original with a jmp + marker, and then replaces the function itself. something like int (*foo)(...) = new_linker_file_lookup_set; */ /* * The table of allowed modules and symbols */ static const char *_default_table ="\ umass.ko umass_devdescrs 4:vendor 4:product 4:rev 2:proto 2:quirks\n\ uscanner.ko uscanner_devs 2:vendor 2:device 4:flags #comment\n\ if_nfe.ko nfe_devs 2:vendor 2:device s:name \n\ if_re.ko re_devs 2:vendor 2:device 4:type s:name \n\ "; #include #include #include #include /* read() */ #include #include #include #include #include #include int verbose = 0; enum _ty_tags { TAG_LEN_MASK = 0x00ffffff, TAG_TYPE_MASK = 0x0f000000, TAG_HOST = 0, TAG_BE = 0x01000000, TAG_LE = 0x02000000, TAG_PTR = 0x03000000, TAG_STRING = 0x04000000, }; #define ARGCOUNT 16 /* * A structure describing the matching entry */ struct match_info { const char *mod_name; const char *symbol; int symbol_len; /* lenght of the symbol_name */ void *dump_addr; /* dump address */ int data_ofs; /* read/write position */ /* the following two are looked up from the kernel */ struct kld_sym_lookup sym; kvm_t *k; /* kvm_open result */ const char *match_buf; /* copy of the description line */ int reclen; /* length of a single record */ int argcount; /* number of arguments */ const char *argdesc[ARGCOUNT]; /* description strings */ int args[ARGCOUNT]; /* ids for arguments */ /* The low 24 bit represent the length, * the high 8 bits represent various flags * .... .... * |''-- 00 = host order * | 01 = big endian * | 10 = little endian * | 11 = pointer * `---- 0 ordinary pointer * 1 string */ }; enum errcode { E_NO_ARGS, E_INVALID_MODULE, E_NO_SYMBOL, E_NO_ROOT, E_NO_MEMORY, E_NO_OFS, E_BAD_OFS, E_BAD_ARGS, E_UNSUPPORTED, E_WRITE_ERROR, E_FILE, }; /* print an error message */ static void help(enum errcode err, struct match_info *m) { switch (err) { case E_NO_ARGS: fprintf(stderr, "Need at least two arguments\n"); break; case E_INVALID_MODULE: fprintf(stderr, "Invalid module name %s\n", m->mod_name); break; case E_NO_SYMBOL: fprintf(stderr, "Symbol %s not found in module %s\n", m->symbol, m->mod_name); break; case E_NO_ROOT: fprintf(stderr, "Must be root to use kvm_read/kvm_write\n"); break; case E_NO_OFS: fprintf(stderr, "Missing write offset\n"); break; case E_BAD_OFS: fprintf(stderr, "Bad write offset\n"); break; case E_NO_MEMORY: perror("malloc failure"); break; case E_BAD_ARGS: fprintf(stderr, "Invalid argument length\n"); break; case E_UNSUPPORTED: fprintf(stderr, "Operand size unsupported\n"); break; case E_WRITE_ERROR: fprintf(stderr, "Error writing to kvm\n"); break; case E_FILE: fprintf(stderr, "Error opening/reading file\n"); break; } exit(1); } /* wrapper around calloc: try an allocation, exit on error */ static void * my_calloc(int size) { void *res = calloc(1, size); if (!res) help(E_NO_MEMORY, NULL); return res; } /* * print the record in raw or decoded mode */ static void dump(unsigned char *buf, struct match_info *m) { int i, n, l; if (verbose) fprintf(stderr, "-- %d records of size %d, %d fields\n", m->sym.symsize / m->reclen, m->reclen, m->argcount); /* print headers */ fprintf(stderr, "Index "); for (i = 0; i < m->argcount; i++) { l = m->args[i] & TAG_LEN_MASK; l = 2*l + 2; /* field length */ fprintf(stderr, "%*s ", l, m->argdesc[i] ? m->argdesc[i] : ""); } fprintf(stderr, "\n"); for (n = 0; n < m->sym.symsize / m->reclen; n++) { const unsigned char *p = buf + n * m->reclen; if (m->dump_addr && m->dump_addr != p) { continue; } fprintf(stderr, "%4d: ", n); for (i = 0; i < m->argcount; i++) { uint16_t d16; uint32_t d32; uint64_t d64; int t = m->args[i] & TAG_TYPE_MASK; l = m->args[i] & TAG_LEN_MASK; if (t == TAG_STRING) { const char *s; char buf[65]; bzero(buf, sizeof(buf)); bcopy(p, &s, l); kvm_read(m->k, (unsigned long)s, buf, sizeof(buf) - 1); fprintf(stderr, "%s ", buf); continue; } switch (l) { default: fprintf(stderr, "arg size not recognised %d\n", l); break; case 2: bcopy(p, &d16, l); if (t & TAG_BE) d16 = be16toh(d16); else if (t & TAG_LE) d16 = le16toh(d16); fprintf(stderr, "0x%04x ", d16); break; case 4: bcopy(p, &d32, l); if (t & TAG_BE) d32 = be32toh(d32); else if (t & TAG_LE) d32 = le32toh(d32); fprintf(stderr, "0x%08x ", d32); break; case 8: bcopy(p, &d64, l); if (t & TAG_BE) d64 = be64toh(d64); else if (t & TAG_LE) d64 = le64toh(d64); fprintf(stderr, "0x%016llx ", d64); break; } p += l; } fprintf(stderr, "\n"); } return; #if 0 /* hexdump code */ for (i = 0; i < m->sym.symsize; i++) { if ( (i & 15) == 0) printf("\n%04x: ", i); printf("%02x ", buf[i]); if ( (i & 15) == 7) printf(" "); } printf("\n"); #endif } /* * Match name and/or symbol with the content of the table. * All input and output fields are in *m. * Return the record filled with info from the matching entry. * Use NULL, "" or "-" to use a wildcard on name or symbol. * Retval is 0 on failure, 1 on match */ static int match_table(const char *table, struct match_info *m) { const char *c, *s; char *s1; int ll; unsigned i; int mod_len; /* length of module name */ int sym_len; /* length of symbol name */ if (!table || !m) return 0; /* store the length, set to 0 for wildcard */ mod_len = m->mod_name ? strlen(m->mod_name) : 0; if (mod_len == 1 && m->mod_name[0] == '-') mod_len = 0; sym_len = m->symbol ? strlen(m->symbol) : 0; if (sym_len == 1 && m->symbol[0] == '-') sym_len = 0; /* match module and symbol. On return, s and c point to the matching * module and symbol name, lengths are in mod_len and sym_len. */ for (s = table; *s; s += ll + 1) { int ln, ls; s += strspn(s, " \t"); /* skip blanks */ ll = strcspn(s, "\n"); /* remaining line length */ if (verbose) fprintf(stderr, "Analysing: [%.*s]\n", ll, s); if (*s == '\0') /* table complete */ break; ln = strcspn(s, " \t#\n"); /* module name length in table */ if (mod_len == 0) { /* no module name, assume a match */ } else if (mod_len == ln && !strncmp(s, m->mod_name, mod_len)) { /* exact match on module name */ } else if (ln > 3 && mod_len == ln - 3 && !strncmp(s, m->mod_name, mod_len) && !strncmp(s + ln - 3, ".ko", 3) ) { /* match without .ko suffix */ } else { continue; /* no match */ } c = s + ln; /* skip the module name */ c += strspn(c, " \t"); /* skip separators */ ls = strcspn(c, " \t#\n"); /* length of symbol name */ if (*c == '\0') continue; /* no symbol, fail at next cycle */ if (!sym_len || (ls == sym_len && !strncmp(c, m->symbol, sym_len))) { sym_len = ls; mod_len = ln; break; } } if (*s == '\0') return 0; /* * make a copy of the matching line to fetch module and symbol * names and comment fields. */ m->match_buf = s1 = my_calloc(ll+1); strncpy(s1, s, ll); if (verbose) { fprintf(stderr, "found: [%.*s]\n", ll, s); fprintf(stderr, "copy: [%s]\n", s1); fprintf(stderr, " name %.*s symbol %.*s\n", mod_len, s, sym_len, c); } /* store the matching values in the return struct */ m->mod_name = s1; s1[mod_len] = '\0'; /* truncate module name */ m->symbol = s1 + (c - s); s1[c + sym_len - s] = '\0'; /* truncate symbol name */ s1 += c + sym_len - s + 1; s1 += strspn(s1, " \t"); /* now scan the argument list extracting type, size and description */ m->reclen = 0; /* total record length so far */ ll = sizeof(m->args)/sizeof(m->args[0]); /* size of argument list */ for (i=0; !index("#\n", *s1) && i < ll; i++) { char c, *endp; m->args[i] = strtoul(s1, &endp, 0); c = *endp++; switch (c) { default: endp--; /* not recognised */ break; case 'b': m->args[i] |= TAG_BE; break; case 'l': m->args[i] |= TAG_LE; break; case 'p': case 's': if (m->args[i] == 0) m->args[i] = sizeof(void *); m->args[i] |= (c == 's') ? TAG_STRING : TAG_PTR; break; } if (*endp == ':') /* comment up to the next space */ m->argdesc[i] = endp + 1; m->reclen += m->args[i] & TAG_LEN_MASK; if (verbose) fprintf(stderr, "found entry 0x%x\n", m->args[i]); s1 += strcspn(s1, " \t\n#"); /* skip descriptor */ if (*s1 == '\n') break; if (*s1) *s1++ = '\0'; /* truncate the string */ s1 += strspn(s1, " \t"); /* skip whitespace */ } *s1 = '\0'; /* truncate the string */ m->argcount = i; if (verbose) fprintf(stderr, "found: name [%s] sym [%s] reclen %d argcount %d\n", m->mod_name, m->symbol, m->reclen, m->argcount); return 1; } int (*foo)(int a, int b); void c() { asm("jmp foo"); } static int do_rw(struct match_info *m, int argc, char *argv[]); /* * usage: program [options] module_name symbol [@recno [val ... ]] * where options are * -v: verbose * -t file external table * -m moduledir use module directory */ int main(int argc, char *argv[]) { char *module, *sym; const char *table = _default_table; struct match_info m; bzero(&m, sizeof(m)); if (argc > 1 && !strcmp(argv[1], "-v")) { verbose = 1; argc--; argv++; } if (argc > 2 && !strcmp(argv[1], "-t")) { /* either a filename or the table line */ int fd = open(argv[2], O_RDONLY); if (fd < 0) { /* assume a single line */ table = argv[2]; } else { int l = lseek(fd, 0, SEEK_END); if (l > 100000) /* 100k is way too much for a table */ help(E_FILE, NULL); lseek(fd, 0, SEEK_SET); table = my_calloc(l); read(fd, (char *)table, l); close(fd); } argc -= 2; argv += 2; } if (argc < 3) help(E_NO_ARGS, NULL); /* need module and symbol */ /* argc==3 -> read all; argc==4 -> read one, argc>4 -> write */ module = argv[1]; sym = argv[2]; if (argc > 3) { if (argv[3][0] != '@') help(E_NO_OFS, &m); m.data_ofs = atoi(argv[3] + 1); } m.mod_name = module; m.symbol = sym; /* lookup the symbol */ if (!match_table(table, &m)) help(E_INVALID_MODULE, &m); if (argc > 4 && argc - 4 != m.argcount) help(E_BAD_ARGS, &m); do_rw(&m, argc, argv); return 0; } /* * read and possibly write into the module */ static int do_rw(struct match_info *m, int argc, char *argv[]) { unsigned char *buf, *srcbuf; int kid, i, l, bufp; if (verbose) fprintf(stderr, "kldfind %s %s\n", m->mod_name, m->symbol); kid = kldfind(m->mod_name); if (kid == -1) { fprintf(stderr, "module %s not found, try kernel\n", m->mod_name); kid = 0; } m->sym.version = sizeof(struct kld_sym_lookup); m->sym.symname = (char *)m->symbol; /* XXX why not const ? */ m->sym.symvalue = 0; m->sym.symsize = 0; if (kldsym(kid, KLDSYM_LOOKUP, &m->sym)) help(E_NO_SYMBOL, m); if (verbose) fprintf(stderr, "found %s at 0x%lx len %d\n", m->symbol, m->sym.symvalue, m->sym.symsize); if (argc > 3) { /* compute the data offset, check bounds */ m->data_ofs *= m->reclen; if (m->data_ofs < 0) m->data_ofs = m->sym.symsize + m->data_ofs; if (verbose) fprintf(stderr, " data ofs %d\n", m->data_ofs); if (m->data_ofs < 0 || m->data_ofs > (int)m->sym.symsize - m->reclen) help(E_BAD_OFS, m); } buf = my_calloc(m->sym.symsize); m->k = kvm_open(NULL, NULL, NULL, (argc < 4) ? O_RDONLY : O_RDWR, "lr:"); if (m->k == NULL || kvm_read(m->k, m->sym.symvalue, buf, m->sym.symsize) != (int)m->sym.symsize) help(E_NO_ROOT, m); if (verbose) fprintf(stderr, " got data\n"); if (argc <= 4) { /* read mode */ m->dump_addr = (argc != 4) ? NULL : buf + m->data_ofs; dump(buf, m); exit(0); } /* * allocate the write buffer, dup the existing content and then * copy arguments passed from the command line */ srcbuf = my_calloc(m->reclen); bcopy(buf + m->data_ofs, srcbuf, m->reclen); // prepare to copy in memory in endian-safe way for (i = 0, bufp = 0; i < m->argcount && i+4 < argc; i++, bufp += l) { uint32_t d32; uint16_t d16; l = m->args[i] & TAG_LEN_MASK; if (m->args[i] & (TAG_PTR | TAG_LE | TAG_BE)) help(E_UNSUPPORTED, m); if ( m->args[i] & (TAG_PTR | TAG_STRING) ) { /* do not overwrite strings or pointers */ fprintf(stderr, "skip string/pointer %d:%s\n", i, argv[i+4]); } else { switch (l) { default: fprintf(stderr, "unsupported format 0x%x, exit\n", m->args[i]); exit(1); case 4: d32 = strtoul(argv[i+4], NULL, 0); if (m->args[i] & TAG_LE) d32 = htole32(d32); else if (m->args[i] & TAG_BE) d32 = htobe32(d32); bcopy(&d32, srcbuf+bufp, sizeof(d32)); break; case 2: d16 = strtoul(argv[i+4], NULL, 0); if (m->args[i] & TAG_LE) d16 = htole16(d16); else if (m->args[i] & TAG_BE) d16 = htobe16(d16); bcopy(&d16, srcbuf+bufp, sizeof(d16)); break; } } } if (bufp != m->reclen) help(E_BAD_ARGS, m); if (kvm_write(m->k, m->sym.symvalue + m->data_ofs, srcbuf, m->reclen) != m->reclen) help(E_WRITE_ERROR, m); if (kvm_read(m->k, m->sym.symvalue, buf, m->sym.symsize) != (int)m->sym.symsize) help(E_NO_ROOT, m); dump(buf, m); return 0; } kmodpatch/kmodpatch.8000644 000423 000000 00000014065 11127221170 015331 0ustar00luigiwheel000000 000000 .\" Copyright (c) 2009 Luigi Rizzo .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND .\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE .\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE .\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL .\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS .\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT .\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" .\" $FreeBSD: $ .\" .Dd January 1, 2009 .Dt KMODPATCH 8 .Os .Sh NAME .Nm kmodpatch .Nd print/modify device/quirk tables in kernel modules .Sh SYNOPSIS .Nm .Op Fl v .Op Fl t Ar file | format .Ar module_name table_name Op @offset Op args ... .Sh DESCRIPTION The .Nm utility can print or alter the content of device/quirk tables in kernel modules. These tables are generally used to identify devices, and possibly apply specific quirks to enable/disable certain features. .Nm is especially useful to let the kernel recognise a new device without rebooting and rebuilding/reinstalling kernel or modules. .Pp In read mode (when no .Ar args are specified), .Nm only list one or more entries in the matching module and table. .Pp In write mode, .Nm overwrites a user's specified entry in a table with the values passed on the command line. .Nm does some amount of checking to make sure that the arguments (module and table name, offset, parameters formats) are compatible with the currently loaded modules. .Pp A couple of real life examples: .Pp .Bl -tag -width mm .It kmodpatch umass.ko - @0 0x4050 0x4a5 0x0101 0x4200 set the kernel to use flags UMASS_PROTO_SCSI | UMASS_PROTO_BBB and quirks WRONG_CSWSIG | NO_SYNCHRONIZE_CACHE for a certain GSM phone that implements the umass interface; .Pp .It kmodpatch uscanner.ko - @0 0x04b8 0x084a 0 let uscanner.ko recognise a newly introduced MFP scanner device. .El .Pp .Nm relies on a list of which modules and tables (associated to kernel symbols) it can work on, and the format of the records in each table. By default .Nm uses an internal list, which can be overridden from the command line using the .Fl t option followed by either a file name, or by immediate text describing the kernel table. The format of the file is described in Section .Sx MODULE DESCRIPTION .Pp The following options are available: .Bl -tag -width indent .It Fl v Verbose mode, print some debugging info .It Fl t Ar file | format Specify a file containing the name and format of descriptor tables, or a line with the actual format of the table. .Pp .Sh MODULE DESCRIPTION The file (or text) describing modules, tables and record structure has a simple text format with one line per table. The `#' character is a comment delimiter, and can appear anywhere in the text. Each valid line must contain the following fields: .Bd -offset indent .Cm module_name .Cm symbol_name .Cm entry_format Op entry_format ... .Ed .Pp Each .Cm entry_format describes one field in the table, and it is made of a number and/or a character specifying the field size and format, followed by an optional ':' and a word describing the field itself. .Pp Supported formats are: .Bl -tag -width indent .It 2 | 2l | 2b 16-bit unsigned in host, little-endian or big-endian format; .It 4 | 4l | 4b 32-bit unsigned in host, little-endian or big-endian format; .It 8 | 8l | 8b 64-bit unsigned in host, little-endian or big-endian format; .It p a pointer; .It s a null-terminated string; .El .Pp The optional description is used as a header when listing the content of a table. .Pp .Ss EXAMPLE MODULE DESCRIPTION .Pp The following is an example of a file containing module descriptions. .Pp .Bd -literal -offset indent umass.ko umass_devdescrs 4:vendor 4:product 4:rev 2:proto 2:quirks uscanner.ko uscanner_devs 2:vendor 2:device 4:flags # comment if_nfe.ko nfe_devs 2:vendor 2:device s:name if_re.ko re_devs 2:vendor 2:device 4:type s:name .Ed .Pp .Sh EXAMPLES .Pp In addition to the two examples given at the beginning, one can use .Nm to explore the content of kernel tables, as follows. .Pp To list entries in module if_re: .Pp .Dl "kmodpatch if_re -" .Pp To list entry 10 in module umass: .Pp .Dl "kmodpatch umass - @10" .Pp To set the last entry in module uscanner.ko .Pp .Dl "kmodpatch uscanner.ko - @-1 0x04b8 0x0839 0" .Pp .Sh WARNINGS .Pp .Nm requires root privileges even to just read the content of the kernel tables. .Pp In write mode, the use of .Nm is as dangerous as accessing /dev/kmem in write mode, even though .Nm does some amount of checking to make sure that the arguments passed are reasonable. To this purpose, it is fundamental that the table descriptions used by .Nm match the actual structure of the kernel tables. There is no automatic way to extract this info. .Pp .Sh BUGS .Pp Right now, .Nm only writes to in-memory modules (or kernel). As a consequence the approach is not suitable to devices that are persistently connected to the system and for which the system cannot do a "reprobe". .Pp .Sh SEE ALSO .Xr kldconfig 8 , .Xr kldload 8 , .Xr kldstat 8 , .Xr kldunload 8 .Sh HISTORY The .Nm utility first appeared in .Fx 8.0 inspired by a similar feature present on Linux. .Sh AUTHORS .An -nosplit The .Nm utility was designed and implemented by .An Luigi Rizzo Aq luigi@FreeBSD.org .