diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e50f0168..a5caf269 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,6 +60,10 @@ jobs: make mv syscallhook.kpm demo-syscallhook.kpm + cd ../demo-kconfig + make + mv kconfig.kpm demo-kconfig.kpm + - name: Upload elf uses: actions/upload-artifact@v4 with: @@ -79,6 +83,7 @@ jobs: kpms/demo-hello/demo-hello.kpm kpms/demo-inlinehook/demo-inlinehook.kpm kpms/demo-syscallhook/demo-syscallhook.kpm + kpms/demo-kconfig/demo-kconfig.kpm generateReleaseNotes: true allowUpdates: true replacesArtifacts: true diff --git a/.github/workflows/build_dev.yml b/.github/workflows/build_dev.yml index 312494b8..d8423ba5 100644 --- a/.github/workflows/build_dev.yml +++ b/.github/workflows/build_dev.yml @@ -76,6 +76,10 @@ jobs: make mv syscallhook.kpm demo-syscallhook.kpm + cd ../demo-kconfig + make + mv kconfig.kpm demo-kconfig.kpm + - name: Upload elf uses: actions/upload-artifact@v3 with: @@ -95,6 +99,7 @@ jobs: kpms/demo-hello/demo-hello.kpm kpms/demo-inlinehook/demo-inlinehook.kpm kpms/demo-syscallhook/demo-syscallhook.kpm + kpms/demo-kconfig/demo-kconfig.kpm generateReleaseNotes: true allowUpdates: true replacesArtifacts: true diff --git a/README.md b/README.md index 95c1211a..a959df67 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ Linux 3.18 - 6.6 (theoretically) - [vmlinux-to-elf](https://github.com/marin-m/vmlinux-to-elf): Some ideas for parsing kernel symbols. - [android-inline-hook](https://github.com/bytedance/android-inline-hook): Some code for fixing arm64 inline hook instructions. - [tlsf](https://github.com/mattconte/tlsf): Memory allocator used for KPM. (Need another to allocate ROX memory.) +- [puff](https://github.com/madler/zlib/tree/master/contrib/puff): Minimal inflate (zlib License) used to decompress the kernel's CONFIG_IKCONFIG data for KPM kernel config resolution. ## License diff --git a/kernel/Makefile b/kernel/Makefile index f1aa36fa..ed371001 100644 --- a/kernel/Makefile +++ b/kernel/Makefile @@ -35,8 +35,10 @@ BASE_SRCS += base/hotpatch.c BASE_SRCS += base/hmem.c BASE_SRCS += base/predata.c BASE_SRCS += base/symbol.c -BASE_SRCS += base/baselib.c -BASE_SRCS += base/sha256.c +BASE_SRCS += base/baselib.c +BASE_SRCS += base/sha256.c +BASE_SRCS += base/puff.c +BASE_SRCS += base/setjmp.S BASE_SRCS += $(wildcard patch/*.c) BASE_SRCS += $(wildcard patch/common/*.c) diff --git a/kernel/base/puff.c b/kernel/base/puff.c new file mode 100644 index 00000000..999099fc --- /dev/null +++ b/kernel/base/puff.c @@ -0,0 +1,840 @@ +/* + * puff.c + * Copyright (C) 2002-2013 Mark Adler + * For conditions of distribution and use, see copyright notice in puff.h + * version 2.3, 21 Jan 2013 + * + * puff.c is a simple inflate written to be an unambiguous way to specify the + * deflate format. It is not written for speed but rather simplicity. As a + * side benefit, this code might actually be useful when small code is more + * important than speed, such as bootstrap applications. For typical deflate + * data, zlib's inflate() is about four times as fast as puff(). zlib's + * inflate compiles to around 20K on my machine, whereas puff.c compiles to + * around 4K on my machine (a PowerPC using GNU cc). If the faster decode() + * function here is used, then puff() is only twice as slow as zlib's + * inflate(). + * + * All dynamically allocated memory comes from the stack. The stack required + * is less than 2K bytes. This code is compatible with 16-bit int's and + * assumes that long's are at least 32 bits. puff.c uses the short data type, + * assumed to be 16 bits, for arrays in order to conserve memory. The code + * works whether integers are stored big endian or little endian. + * + * In the comments below are "Format notes" that describe the inflate process + * and document some of the less obvious aspects of the format. This source + * code is meant to supplement RFC 1951, which formally describes the deflate + * format: + * + * https://datatracker.ietf.org/doc/html/rfc1951 + */ + +/* + * Change history: + * + * 1.0 10 Feb 2002 - First version + * 1.1 17 Feb 2002 - Clarifications of some comments and notes + * - Update puff() dest and source pointers on negative + * errors to facilitate debugging deflators + * - Remove longest from struct huffman -- not needed + * - Simplify offs[] index in construct() + * - Add input size and checking, using longjmp() to + * maintain easy readability + * - Use short data type for large arrays + * - Use pointers instead of long to specify source and + * destination sizes to avoid arbitrary 4 GB limits + * 1.2 17 Mar 2002 - Add faster version of decode(), doubles speed (!), + * but leave simple version for readability + * - Make sure invalid distances detected if pointers + * are 16 bits + * - Fix fixed codes table error + * - Provide a scanning mode for determining size of + * uncompressed data + * 1.3 20 Mar 2002 - Go back to lengths for puff() parameters [Gailly] + * - Add a puff.h file for the interface + * - Add braces in puff() for else do [Gailly] + * - Use indexes instead of pointers for readability + * 1.4 31 Mar 2002 - Simplify construct() code set check + * - Fix some comments + * - Add FIXLCODES #define + * 1.5 6 Apr 2002 - Minor comment fixes + * 1.6 7 Aug 2002 - Minor format changes + * 1.7 3 Mar 2003 - Added test code for distribution + * - Added zlib-like license + * 1.8 9 Jan 2004 - Added some comments on no distance codes case + * 1.9 21 Feb 2008 - Fix bug on 16-bit integer architectures [Pohland] + * - Catch missing end-of-block symbol error + * 2.0 25 Jul 2008 - Add #define to permit distance too far back + * - Add option in TEST code for puff to write the data + * - Add option in TEST code to skip input bytes + * - Allow TEST code to read from piped stdin + * 2.1 4 Apr 2010 - Avoid variable initialization for happier compilers + * - Avoid unsigned comparisons for even happier compilers + * 2.2 25 Apr 2010 - Fix bug in variable initializations [Oberhumer] + * - Add const where appropriate [Oberhumer] + * - Split if's and ?'s for coverage testing + * - Break out test code to separate file + * - Move NIL to puff.h + * - Allow incomplete code only if single code length is 1 + * - Add full code coverage test to Makefile + * 2.3 21 Jan 2013 - Check for invalid code length codes in dynamic blocks + */ + +#include /* for setjmp(), longjmp(), and jmp_buf */ +#include "puff.h" /* prototype for puff() */ + +#define local static /* for local function definitions */ + +/* + * Maximums for allocations and loops. It is not useful to change these -- + * they are fixed by the deflate format. + */ +#define MAXBITS 15 /* maximum bits in a code */ +#define MAXLCODES 286 /* maximum number of literal/length codes */ +#define MAXDCODES 30 /* maximum number of distance codes */ +#define MAXCODES (MAXLCODES+MAXDCODES) /* maximum codes lengths to read */ +#define FIXLCODES 288 /* number of fixed literal/length codes */ + +/* input and output state */ +struct state { + /* output state */ + unsigned char *out; /* output buffer */ + unsigned long outlen; /* available space at out */ + unsigned long outcnt; /* bytes written to out so far */ + + /* input state */ + const unsigned char *in; /* input buffer */ + unsigned long inlen; /* available input at in */ + unsigned long incnt; /* bytes read so far */ + int bitbuf; /* bit buffer */ + int bitcnt; /* number of bits in bit buffer */ + + /* input limit error return state for bits() and decode() */ + jmp_buf env; +}; + +/* + * Return need bits from the input stream. This always leaves less than + * eight bits in the buffer. bits() works properly for need == 0. + * + * Format notes: + * + * - Bits are stored in bytes from the least significant bit to the most + * significant bit. Therefore bits are dropped from the bottom of the bit + * buffer, using shift right, and new bytes are appended to the top of the + * bit buffer, using shift left. + */ +local int bits(struct state *s, int need) +{ + long val; /* bit accumulator (can use up to 20 bits) */ + + /* load at least need bits into val */ + val = s->bitbuf; + while (s->bitcnt < need) { + if (s->incnt == s->inlen) + longjmp(s->env, 1); /* out of input */ + val |= (long)(s->in[s->incnt++]) << s->bitcnt; /* load eight bits */ + s->bitcnt += 8; + } + + /* drop need bits and update buffer, always zero to seven bits left */ + s->bitbuf = (int)(val >> need); + s->bitcnt -= need; + + /* return need bits, zeroing the bits above that */ + return (int)(val & ((1L << need) - 1)); +} + +/* + * Process a stored block. + * + * Format notes: + * + * - After the two-bit stored block type (00), the stored block length and + * stored bytes are byte-aligned for fast copying. Therefore any leftover + * bits in the byte that has the last bit of the type, as many as seven, are + * discarded. The value of the discarded bits are not defined and should not + * be checked against any expectation. + * + * - The second inverted copy of the stored block length does not have to be + * checked, but it's probably a good idea to do so anyway. + * + * - A stored block can have zero length. This is sometimes used to byte-align + * subsets of the compressed data for random access or partial recovery. + */ +local int stored(struct state *s) +{ + unsigned len; /* length of stored block */ + + /* discard leftover bits from current byte (assumes s->bitcnt < 8) */ + s->bitbuf = 0; + s->bitcnt = 0; + + /* get length and check against its one's complement */ + if (s->incnt + 4 > s->inlen) + return 2; /* not enough input */ + len = s->in[s->incnt++]; + len |= s->in[s->incnt++] << 8; + if (s->in[s->incnt++] != (~len & 0xff) || + s->in[s->incnt++] != ((~len >> 8) & 0xff)) + return -2; /* didn't match complement! */ + + /* copy len bytes from in to out */ + if (s->incnt + len > s->inlen) + return 2; /* not enough input */ + if (s->out != NIL) { + if (s->outcnt + len > s->outlen) + return 1; /* not enough output space */ + while (len--) + s->out[s->outcnt++] = s->in[s->incnt++]; + } + else { /* just scanning */ + s->outcnt += len; + s->incnt += len; + } + + /* done with a valid stored block */ + return 0; +} + +/* + * Huffman code decoding tables. count[1..MAXBITS] is the number of symbols of + * each length, which for a canonical code are stepped through in order. + * symbol[] are the symbol values in canonical order, where the number of + * entries is the sum of the counts in count[]. The decoding process can be + * seen in the function decode() below. + */ +struct huffman { + short *count; /* number of symbols of each length */ + short *symbol; /* canonically ordered symbols */ +}; + +/* + * Decode a code from the stream s using huffman table h. Return the symbol or + * a negative value if there is an error. If all of the lengths are zero, i.e. + * an empty code, or if the code is incomplete and an invalid code is received, + * then -10 is returned after reading MAXBITS bits. + * + * Format notes: + * + * - The codes as stored in the compressed data are bit-reversed relative to + * a simple integer ordering of codes of the same lengths. Hence below the + * bits are pulled from the compressed data one at a time and used to + * build the code value reversed from what is in the stream in order to + * permit simple integer comparisons for decoding. A table-based decoding + * scheme (as used in zlib) does not need to do this reversal. + * + * - The first code for the shortest length is all zeros. Subsequent codes of + * the same length are simply integer increments of the previous code. When + * moving up a length, a zero bit is appended to the code. For a complete + * code, the last code of the longest length will be all ones. + * + * - Incomplete codes are handled by this decoder, since they are permitted + * in the deflate format. See the format notes for fixed() and dynamic(). + */ +#ifdef SLOW +local int decode(struct state *s, const struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + + code = first = index = 0; + for (len = 1; len <= MAXBITS; len++) { + code |= bits(s, 1); /* get next bit */ + count = h->count[len]; + if (code - count < first) /* if length len, return symbol */ + return h->symbol[index + (code - first)]; + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + } + return -10; /* ran out of codes */ +} + +/* + * A faster version of decode() for real applications of this code. It's not + * as readable, but it makes puff() twice as fast. And it only makes the code + * a few percent larger. + */ +#else /* !SLOW */ +local int decode(struct state *s, const struct huffman *h) +{ + int len; /* current number of bits in code */ + int code; /* len bits being decoded */ + int first; /* first code of length len */ + int count; /* number of codes of length len */ + int index; /* index of first code of length len in symbol table */ + int bitbuf; /* bits from stream */ + int left; /* bits left in next or left to process */ + short *next; /* next number of codes */ + + bitbuf = s->bitbuf; + left = s->bitcnt; + code = first = index = 0; + len = 1; + next = h->count + 1; + while (1) { + while (left--) { + code |= bitbuf & 1; + bitbuf >>= 1; + count = *next++; + if (code - count < first) { /* if length len, return symbol */ + s->bitbuf = bitbuf; + s->bitcnt = (s->bitcnt - len) & 7; + return h->symbol[index + (code - first)]; + } + index += count; /* else update for next length */ + first += count; + first <<= 1; + code <<= 1; + len++; + } + left = (MAXBITS+1) - len; + if (left == 0) + break; + if (s->incnt == s->inlen) + longjmp(s->env, 1); /* out of input */ + bitbuf = s->in[s->incnt++]; + if (left > 8) + left = 8; + } + return -10; /* ran out of codes */ +} +#endif /* SLOW */ + +/* + * Given the list of code lengths length[0..n-1] representing a canonical + * Huffman code for n symbols, construct the tables required to decode those + * codes. Those tables are the number of codes of each length, and the symbols + * sorted by length, retaining their original order within each length. The + * return value is zero for a complete code set, negative for an over- + * subscribed code set, and positive for an incomplete code set. The tables + * can be used if the return value is zero or positive, but they cannot be used + * if the return value is negative. If the return value is zero, it is not + * possible for decode() using that table to return an error--any stream of + * enough bits will resolve to a symbol. If the return value is positive, then + * it is possible for decode() using that table to return an error for received + * codes past the end of the incomplete lengths. + * + * Not used by decode(), but used for error checking, h->count[0] is the number + * of the n symbols not in the code. So n - h->count[0] is the number of + * codes. This is useful for checking for incomplete codes that have more than + * one symbol, which is an error in a dynamic block. + * + * Assumption: for all i in 0..n-1, 0 <= length[i] <= MAXBITS + * This is assured by the construction of the length arrays in dynamic() and + * fixed() and is not verified by construct(). + * + * Format notes: + * + * - Permitted and expected examples of incomplete codes are one of the fixed + * codes and any code with a single symbol which in deflate is coded as one + * bit instead of zero bits. See the format notes for fixed() and dynamic(). + * + * - Within a given code length, the symbols are kept in ascending order for + * the code bits definition. + */ +local int construct(struct huffman *h, const short *length, int n) +{ + int symbol; /* current symbol when stepping through length[] */ + int len; /* current length when stepping through h->count[] */ + int left; /* number of possible codes left of current length */ + short offs[MAXBITS+1]; /* offsets in symbol table for each length */ + + /* count number of codes of each length */ + for (len = 0; len <= MAXBITS; len++) + h->count[len] = 0; + for (symbol = 0; symbol < n; symbol++) + (h->count[length[symbol]])++; /* assumes lengths are within bounds */ + if (h->count[0] == n) /* no codes! */ + return 0; /* complete, but decode() will fail */ + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; /* one possible code of zero length */ + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; /* one more bit, double codes left */ + left -= h->count[len]; /* deduct count from possible codes */ + if (left < 0) + return left; /* over-subscribed--return negative */ + } /* left > 0 means incomplete */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + h->count[len]; + + /* + * put symbols in table sorted by length, by symbol order within each + * length + */ + for (symbol = 0; symbol < n; symbol++) + if (length[symbol] != 0) + h->symbol[offs[length[symbol]]++] = symbol; + + /* return zero for complete set, positive for incomplete set */ + return left; +} + +/* + * Decode literal/length and distance codes until an end-of-block code. + * + * Format notes: + * + * - Compressed data that is after the block type if fixed or after the code + * description if dynamic is a combination of literals and length/distance + * pairs terminated by and end-of-block code. Literals are simply Huffman + * coded bytes. A length/distance pair is a coded length followed by a + * coded distance to represent a string that occurs earlier in the + * uncompressed data that occurs again at the current location. + * + * - Literals, lengths, and the end-of-block code are combined into a single + * code of up to 286 symbols. They are 256 literals (0..255), 29 length + * symbols (257..285), and the end-of-block symbol (256). + * + * - There are 256 possible lengths (3..258), and so 29 symbols are not enough + * to represent all of those. Lengths 3..10 and 258 are in fact represented + * by just a length symbol. Lengths 11..257 are represented as a symbol and + * some number of extra bits that are added as an integer to the base length + * of the length symbol. The number of extra bits is determined by the base + * length symbol. These are in the static arrays below, lens[] for the base + * lengths and lext[] for the corresponding number of extra bits. + * + * - The reason that 258 gets its own symbol is that the longest length is used + * often in highly redundant files. Note that 258 can also be coded as the + * base value 227 plus the maximum extra value of 31. While a good deflate + * should never do this, it is not an error, and should be decoded properly. + * + * - If a length is decoded, including its extra bits if any, then it is + * followed a distance code. There are up to 30 distance symbols. Again + * there are many more possible distances (1..32768), so extra bits are added + * to a base value represented by the symbol. The distances 1..4 get their + * own symbol, but the rest require extra bits. The base distances and + * corresponding number of extra bits are below in the static arrays dist[] + * and dext[]. + * + * - Literal bytes are simply written to the output. A length/distance pair is + * an instruction to copy previously uncompressed bytes to the output. The + * copy is from distance bytes back in the output stream, copying for length + * bytes. + * + * - Distances pointing before the beginning of the output data are not + * permitted. + * + * - Overlapped copies, where the length is greater than the distance, are + * allowed and common. For example, a distance of one and a length of 258 + * simply copies the last byte 258 times. A distance of four and a length of + * twelve copies the last four bytes three times. A simple forward copy + * ignoring whether the length is greater than the distance or not implements + * this correctly. You should not use memcpy() since its behavior is not + * defined for overlapped arrays. You should not use memmove() or bcopy() + * since though their behavior -is- defined for overlapping arrays, it is + * defined to do the wrong thing in this case. + */ +local int codes(struct state *s, + const struct huffman *lencode, + const struct huffman *distcode) +{ + int symbol; /* decoded symbol */ + int len; /* length for copy */ + unsigned dist; /* distance for copy */ + static const short lens[29] = { /* Size base for length codes 257..285 */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258}; + static const short lext[29] = { /* Extra bits for length codes 257..285 */ + 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, + 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0}; + static const short dists[30] = { /* Offset base for distance codes 0..29 */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577}; + static const short dext[30] = { /* Extra bits for distance codes 0..29 */ + 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, + 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13}; + + /* decode literals and length/distance pairs */ + do { + symbol = decode(s, lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 256) { /* literal: symbol is the byte */ + /* write out the literal */ + if (s->out != NIL) { + if (s->outcnt == s->outlen) + return 1; + s->out[s->outcnt] = symbol; + } + s->outcnt++; + } + else if (symbol > 256) { /* length */ + /* get and compute length */ + symbol -= 257; + if (symbol >= 29) + return -10; /* invalid fixed code */ + len = lens[symbol] + bits(s, lext[symbol]); + + /* get and check distance */ + symbol = decode(s, distcode); + if (symbol < 0) + return symbol; /* invalid symbol */ + dist = dists[symbol] + bits(s, dext[symbol]); +#ifndef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + if (dist > s->outcnt) + return -11; /* distance too far back */ +#endif + + /* copy length bytes from distance bytes back */ + if (s->out != NIL) { + if (s->outcnt + len > s->outlen) + return 1; + while (len--) { + s->out[s->outcnt] = +#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR + dist > s->outcnt ? + 0 : +#endif + s->out[s->outcnt - dist]; + s->outcnt++; + } + } + else + s->outcnt += len; + } + } while (symbol != 256); /* end of block symbol */ + + /* done with a valid fixed or dynamic block */ + return 0; +} + +/* + * Process a fixed codes block. + * + * Format notes: + * + * - This block type can be useful for compressing small amounts of data for + * which the size of the code descriptions in a dynamic block exceeds the + * benefit of custom codes for that block. For fixed codes, no bits are + * spent on code descriptions. Instead the code lengths for literal/length + * codes and distance codes are fixed. The specific lengths for each symbol + * can be seen in the "for" loops below. + * + * - The literal/length code is complete, but has two symbols that are invalid + * and should result in an error if received. This cannot be implemented + * simply as an incomplete code since those two symbols are in the "middle" + * of the code. They are eight bits long and the longest literal/length\ + * code is nine bits. Therefore the code must be constructed with those + * symbols, and the invalid symbols must be detected after decoding. + * + * - The fixed distance codes also have two invalid symbols that should result + * in an error if received. Since all of the distance codes are the same + * length, this can be implemented as an incomplete code. Then the invalid + * codes are detected while decoding. + */ +local int fixed(struct state *s) +{ + static int virgin = 1; + static short lencnt[MAXBITS+1], lensym[FIXLCODES]; + static short distcnt[MAXBITS+1], distsym[MAXDCODES]; + static struct huffman lencode, distcode; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + int symbol; + short lengths[FIXLCODES]; + + /* construct lencode and distcode */ + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + /* literal/length table */ + for (symbol = 0; symbol < 144; symbol++) + lengths[symbol] = 8; + for (; symbol < 256; symbol++) + lengths[symbol] = 9; + for (; symbol < 280; symbol++) + lengths[symbol] = 7; + for (; symbol < FIXLCODES; symbol++) + lengths[symbol] = 8; + construct(&lencode, lengths, FIXLCODES); + + /* distance table */ + for (symbol = 0; symbol < MAXDCODES; symbol++) + lengths[symbol] = 5; + construct(&distcode, lengths, MAXDCODES); + + /* do this just once */ + virgin = 0; + } + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Process a dynamic codes block. + * + * Format notes: + * + * - A dynamic block starts with a description of the literal/length and + * distance codes for that block. New dynamic blocks allow the compressor to + * rapidly adapt to changing data with new codes optimized for that data. + * + * - The codes used by the deflate format are "canonical", which means that + * the actual bits of the codes are generated in an unambiguous way simply + * from the number of bits in each code. Therefore the code descriptions + * are simply a list of code lengths for each symbol. + * + * - The code lengths are stored in order for the symbols, so lengths are + * provided for each of the literal/length symbols, and for each of the + * distance symbols. + * + * - If a symbol is not used in the block, this is represented by a zero as the + * code length. This does not mean a zero-length code, but rather that no + * code should be created for this symbol. There is no way in the deflate + * format to represent a zero-length code. + * + * - The maximum number of bits in a code is 15, so the possible lengths for + * any code are 1..15. + * + * - The fact that a length of zero is not permitted for a code has an + * interesting consequence. Normally if only one symbol is used for a given + * code, then in fact that code could be represented with zero bits. However + * in deflate, that code has to be at least one bit. So for example, if + * only a single distance base symbol appears in a block, then it will be + * represented by a single code of length one, in particular one 0 bit. This + * is an incomplete code, since if a 1 bit is received, it has no meaning, + * and should result in an error. So incomplete distance codes of one symbol + * should be permitted, and the receipt of invalid codes should be handled. + * + * - It is also possible to have a single literal/length code, but that code + * must be the end-of-block code, since every dynamic block has one. This + * is not the most efficient way to create an empty block (an empty fixed + * block is fewer bits), but it is allowed by the format. So incomplete + * literal/length codes of one symbol should also be permitted. + * + * - If there are only literal codes and no lengths, then there are no distance + * codes. This is represented by one distance code with zero bits. + * + * - The list of up to 286 length/literal lengths and up to 30 distance lengths + * are themselves compressed using Huffman codes and run-length encoding. In + * the list of code lengths, a 0 symbol means no code, a 1..15 symbol means + * that length, and the symbols 16, 17, and 18 are run-length instructions. + * Each of 16, 17, and 18 are followed by extra bits to define the length of + * the run. 16 copies the last length 3 to 6 times. 17 represents 3 to 10 + * zero lengths, and 18 represents 11 to 138 zero lengths. Unused symbols + * are common, hence the special coding for zero lengths. + * + * - The symbols for 0..18 are Huffman coded, and so that code must be + * described first. This is simply a sequence of up to 19 three-bit values + * representing no code (0) or the code length for that symbol (1..7). + * + * - A dynamic block starts with three fixed-size counts from which is computed + * the number of literal/length code lengths, the number of distance code + * lengths, and the number of code length code lengths (ok, you come up with + * a better name!) in the code descriptions. For the literal/length and + * distance codes, lengths after those provided are considered zero, i.e. no + * code. The code length code lengths are received in a permuted order (see + * the order[] array below) to make a short code length code length list more + * likely. As it turns out, very short and very long codes are less likely + * to be seen in a dynamic code description, hence what may appear initially + * to be a peculiar ordering. + * + * - Given the number of literal/length code lengths (nlen) and distance code + * lengths (ndist), then they are treated as one long list of nlen + ndist + * code lengths. Therefore run-length coding can and often does cross the + * boundary between the two sets of lengths. + * + * - So to summarize, the code description at the start of a dynamic block is + * three counts for the number of code lengths for the literal/length codes, + * the distance codes, and the code length codes. This is followed by the + * code length code lengths, three bits each. This is used to construct the + * code length code which is used to read the remainder of the lengths. Then + * the literal/length code lengths and distance lengths are read as a single + * set of lengths using the code length codes. Codes are constructed from + * the resulting two sets of lengths, and then finally you can start + * decoding actual compressed data in the block. + * + * - For reference, a "typical" size for the code description in a dynamic + * block is around 80 bytes. + */ +local int dynamic(struct state *s) +{ + int nlen, ndist, ncode; /* number of lengths in descriptor */ + int index; /* index of lengths[] */ + int err; /* construct() return value */ + short lengths[MAXCODES]; /* descriptor code lengths */ + short lencnt[MAXBITS+1], lensym[MAXLCODES]; /* lencode memory */ + short distcnt[MAXBITS+1], distsym[MAXDCODES]; /* distcode memory */ + struct huffman lencode, distcode; /* length and distance codes */ + static const short order[19] = /* permutation of code length codes */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + /* construct lencode and distcode */ + lencode.count = lencnt; + lencode.symbol = lensym; + distcode.count = distcnt; + distcode.symbol = distsym; + + /* get number of lengths in each table, check lengths */ + nlen = bits(s, 5) + 257; + ndist = bits(s, 5) + 1; + ncode = bits(s, 4) + 4; + if (nlen > MAXLCODES || ndist > MAXDCODES) + return -3; /* bad counts */ + + /* read code length code lengths (really), missing lengths are zero */ + for (index = 0; index < ncode; index++) + lengths[order[index]] = bits(s, 3); + for (; index < 19; index++) + lengths[order[index]] = 0; + + /* build huffman table for code lengths codes (use lencode temporarily) */ + err = construct(&lencode, lengths, 19); + if (err != 0) /* require complete code set here */ + return -4; + + /* read length/literal and distance code length tables */ + index = 0; + while (index < nlen + ndist) { + int symbol; /* decoded value */ + int len; /* last length to repeat */ + + symbol = decode(s, &lencode); + if (symbol < 0) + return symbol; /* invalid symbol */ + if (symbol < 16) /* length in 0..15 */ + lengths[index++] = symbol; + else { /* repeat instruction */ + len = 0; /* assume repeating zeros */ + if (symbol == 16) { /* repeat last length 3..6 times */ + if (index == 0) + return -5; /* no last length! */ + len = lengths[index - 1]; /* last length */ + symbol = 3 + bits(s, 2); + } + else if (symbol == 17) /* repeat zero 3..10 times */ + symbol = 3 + bits(s, 3); + else /* == 18, repeat zero 11..138 times */ + symbol = 11 + bits(s, 7); + if (index + symbol > nlen + ndist) + return -6; /* too many lengths! */ + while (symbol--) /* repeat last or zero symbol times */ + lengths[index++] = len; + } + } + + /* check for end-of-block code -- there better be one! */ + if (lengths[256] == 0) + return -9; + + /* build huffman table for literal/length codes */ + err = construct(&lencode, lengths, nlen); + if (err && (err < 0 || nlen != lencode.count[0] + lencode.count[1])) + return -7; /* incomplete code ok only for single length 1 code */ + + /* build huffman table for distance codes */ + err = construct(&distcode, lengths + nlen, ndist); + if (err && (err < 0 || ndist != distcode.count[0] + distcode.count[1])) + return -8; /* incomplete code ok only for single length 1 code */ + + /* decode data until end-of-block code */ + return codes(s, &lencode, &distcode); +} + +/* + * Inflate source to dest. On return, destlen and sourcelen are updated to the + * size of the uncompressed data and the size of the deflate data respectively. + * On success, the return value of puff() is zero. If there is an error in the + * source data, i.e. it is not in the deflate format, then a negative value is + * returned. If there is not enough input available or there is not enough + * output space, then a positive error is returned. In that case, destlen and + * sourcelen are not updated to facilitate retrying from the beginning with the + * provision of more input data or more output space. In the case of invalid + * inflate data (a negative error), the dest and source pointers are updated to + * facilitate the debugging of deflators. + * + * puff() also has a mode to determine the size of the uncompressed output with + * no output written. For this dest must be (unsigned char *)0. In this case, + * the input value of *destlen is ignored, and on return *destlen is set to the + * size of the uncompressed output. + * + * The return codes are: + * + * 2: available inflate data did not terminate + * 1: output space exhausted before completing inflate + * 0: successful inflate + * -1: invalid block type (type == 3) + * -2: stored block length did not match one's complement + * -3: dynamic block code description: too many length or distance codes + * -4: dynamic block code description: code lengths codes incomplete + * -5: dynamic block code description: repeat lengths with no first length + * -6: dynamic block code description: repeat more than specified lengths + * -7: dynamic block code description: invalid literal/length code lengths + * -8: dynamic block code description: invalid distance code lengths + * -9: dynamic block code description: missing end-of-block code + * -10: invalid literal/length or distance code in fixed or dynamic block + * -11: distance is too far back in fixed or dynamic block + * + * Format notes: + * + * - Three bits are read for each block to determine the kind of block and + * whether or not it is the last block. Then the block is decoded and the + * process repeated if it was not the last block. + * + * - The leftover bits in the last byte of the deflate data after the last + * block (if it was a fixed or dynamic block) are undefined and have no + * expected values to check. + */ +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen) /* amount of input available */ +{ + struct state s; /* input/output state */ + int last, type; /* block information */ + int err; /* return value */ + + /* initialize output state */ + s.out = dest; + s.outlen = *destlen; /* ignored if dest is NIL */ + s.outcnt = 0; + + /* initialize input state */ + s.in = source; + s.inlen = *sourcelen; + s.incnt = 0; + s.bitbuf = 0; + s.bitcnt = 0; + + /* return if bits() or decode() tries to read past available input */ + if (setjmp(s.env) != 0) /* if came back here via longjmp() */ + err = 2; /* then skip do-loop, return error */ + else { + /* process blocks until last block or error */ + do { + last = bits(&s, 1); /* one if last block */ + type = bits(&s, 2); /* block type 0..3 */ + err = type == 0 ? + stored(&s) : + (type == 1 ? + fixed(&s) : + (type == 2 ? + dynamic(&s) : + -1)); /* type == 3, invalid */ + if (err != 0) + break; /* return with error */ + } while (!last); + } + + /* update the lengths and return */ + if (err <= 0) { + *destlen = s.outcnt; + *sourcelen = s.incnt; + } + return err; +} diff --git a/kernel/base/setjmp.S b/kernel/base/setjmp.S new file mode 100644 index 00000000..d96d1642 --- /dev/null +++ b/kernel/base/setjmp.S @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 mhmrdd. All Rights Reserved. + * + * Minimal freestanding setjmp/longjmp for AArch64, used by the vendored puff.c + * inflate. Only the callee-saved general registers, frame pointer, link + * register and stack pointer are preserved. The engine is built with + * -mgeneral-regs-only, so there are no callee-saved SIMD/FP registers. + * + * jmp_buf layout (uint64_t[16]): + * [0..1] x19 x20 [2..3] x21 x22 [4..5] x23 x24 + * [6..7] x25 x26 [8..9] x27 x28 [10] x29 [11] x30 [12] sp + */ + +.text + +.align 2 +.global setjmp +.type setjmp, %function +setjmp: + stp x19, x20, [x0, #0] + stp x21, x22, [x0, #16] + stp x23, x24, [x0, #32] + stp x25, x26, [x0, #48] + stp x27, x28, [x0, #64] + stp x29, x30, [x0, #80] + mov x1, sp + str x1, [x0, #96] + mov x0, #0 + ret + +.align 2 +.global longjmp +.type longjmp, %function +longjmp: + ldp x19, x20, [x0, #0] + ldp x21, x22, [x0, #16] + ldp x23, x24, [x0, #32] + ldp x25, x26, [x0, #48] + ldp x27, x28, [x0, #64] + ldp x29, x30, [x0, #80] + ldr x2, [x0, #96] + mov sp, x2 + // setjmp() must observe a non-zero result when resumed via longjmp(), + // so a val of 0 is promoted to 1 + cmp w1, #0 + csinc w0, w1, wzr, ne + ret diff --git a/kernel/base/setup1.S b/kernel/base/setup1.S index 386fb7ba..c366ac59 100644 --- a/kernel/base/setup1.S +++ b/kernel/base/setup1.S @@ -99,6 +99,14 @@ start_prepare: ldr x13, [x10, #setup_symbol_lookup_anchor_offset_offset] str x13, [x11, #start_symbol_lookup_anchor_offset_offset] + // start_preset.kconfig_offset = setup_preset.kconfig_offset + ldr x13, [x10, #setup_kconfig_offset_offset] + str x13, [x11, #start_kconfig_offset_offset] + + // start_preset.kconfig_size = setup_preset.kconfig_size + ldr x13, [x10, #setup_kconfig_size_offset] + str x13, [x11, #start_kconfig_size_offset] + // memcpy(&start_preset.setup, &setup_header, KP_HEADER_SIZE); add x0, x11, #start_header_offset add x1, x12, #0 diff --git a/kernel/base/start.c b/kernel/base/start.c index e2826c1b..ee7e973e 100644 --- a/kernel/base/start.c +++ b/kernel/base/start.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -569,6 +570,15 @@ static int start_init(uint64_t kimage_voff, uint64_t linear_voff) log_boot("KernelPatch link base: %llx, runtime base: %llx\n", link_base_addr, runtime_base_addr); + if (start_preset.kconfig_offset && start_preset.kconfig_size) { + kp_kconfig_data = (const void *)(kernel_va + start_preset.kconfig_offset); + kp_kconfig_data_size = (unsigned long)start_preset.kconfig_size; + log_boot("ikconfig offset: %llx, size: %llx\n", (uint64_t)start_preset.kconfig_offset, + (uint64_t)start_preset.kconfig_size); + } else { + log_boot("ikconfig: not present\n"); + } + kallsyms_on_each_symbol = (typeof(kallsyms_on_each_symbol))kallsyms_lookup_name("kallsyms_on_each_symbol"); kernel_kallsyms_on_each_match_symbol = (typeof(kernel_kallsyms_on_each_match_symbol))kallsyms_lookup_name("kallsyms_on_each_match_symbol"); diff --git a/kernel/base/start.h b/kernel/base/start.h index e761a72f..9fc3f28c 100644 --- a/kernel/base/start.h +++ b/kernel/base/start.h @@ -22,6 +22,8 @@ typedef struct int64_t map_offset; int64_t sprintf_offset; int64_t symbol_lookup_anchor_offset; + int64_t kconfig_offset; + int64_t kconfig_size; int64_t map_backup_len; uint8_t map_backup[MAP_MAX_SIZE]; uint8_t superkey[SUPER_KEY_LEN]; @@ -39,7 +41,9 @@ typedef struct #define start_map_offset_offset (start_kernel_pa_offset + 8) #define start_sprintf_offset_offset (start_map_offset_offset + 8) #define start_symbol_lookup_anchor_offset_offset (start_sprintf_offset_offset + 8) -#define start_map_backup_len_offset (start_symbol_lookup_anchor_offset_offset + 8) +#define start_kconfig_offset_offset (start_symbol_lookup_anchor_offset_offset + 8) +#define start_kconfig_size_offset (start_kconfig_offset_offset + 8) +#define start_map_backup_len_offset (start_kconfig_size_offset + 8) #define start_map_backup_offset (start_map_backup_len_offset + 8) #define start_superkey_offset (start_map_backup_offset + MAP_MAX_SIZE) #define start_root_superkey_offset (start_superkey_offset + SUPER_KEY_LEN) diff --git a/kernel/include/kpmodule.h b/kernel/include/kpmodule.h index 21908d38..b5b77316 100644 --- a/kernel/include/kpmodule.h +++ b/kernel/include/kpmodule.h @@ -6,6 +6,8 @@ #ifndef _KP_KPMODULE_H_ #define _KP_KPMODULE_H_ +#include + #define KPM_INFO(name, info, limit) \ _Static_assert(sizeof(info) <= limit, "Info string too long"); \ static const char __kpm_info_##name[] __attribute__((__used__)) \ @@ -41,4 +43,82 @@ typedef long (*mod_exitcall_t)(void *reserved); #define KPM_EXIT(fn) \ static mod_exitcall_t __kpm_exitcall_##fn __attribute__((__used__)) __attribute__((__section__(".kpm.exit"))) = fn +/* + * CONFIG_IKCONFIG resolution. kpm_kconfig_get() returns the option value + * (string options unquoted) or NULL when unset/absent/no-ikconfig. The typed + * wrappers are KPM-side. The KPM_KCONFIG_* macros take an unquoted option name. + */ +const char *kpm_kconfig_get(const char *key); + +enum +{ + KPM_KCONFIG_N = 0, + KPM_KCONFIG_M = 1, + KPM_KCONFIG_Y = 2, +}; + +static inline int kpm_kconfig_tristate(const char *key) +{ + const char *v = kpm_kconfig_get(key); + if (v && v[0] == 'y' && !v[1]) return KPM_KCONFIG_Y; + if (v && v[0] == 'm' && !v[1]) return KPM_KCONFIG_M; + return KPM_KCONFIG_N; +} + +static inline bool kpm_kconfig_bool(const char *key) +{ + const char *v = kpm_kconfig_get(key); + return v && v[0] == 'y' && !v[1]; +} + +static inline bool kpm_kconfig_int(const char *key, long *out) +{ + const char *v = kpm_kconfig_get(key); + if (!v || !out) return false; + bool neg = false; + long n = 0; + if (*v == '-') { + neg = true; + v++; + } else if (*v == '+') { + v++; + } + if (!*v) return false; + for (; *v; v++) { + if (*v < '0' || *v > '9') return false; + n = n * 10 + (*v - '0'); + } + *out = neg ? -n : n; + return true; +} + +static inline bool kpm_kconfig_hex(const char *key, unsigned long *out) +{ + const char *v = kpm_kconfig_get(key); + if (!v || !out) return false; + if (v[0] == '0' && (v[1] == 'x' || v[1] == 'X')) v += 2; + if (!*v) return false; + unsigned long n = 0; + for (; *v; v++) { + unsigned int d; + if (*v >= '0' && *v <= '9') + d = *v - '0'; + else if (*v >= 'a' && *v <= 'f') + d = *v - 'a' + 10; + else if (*v >= 'A' && *v <= 'F') + d = *v - 'A' + 10; + else + return false; + n = (n << 4) | d; + } + *out = n; + return true; +} + +#define KPM_KCONFIG_GET(key) kpm_kconfig_get(#key) +#define KPM_KCONFIG_BOOL(key) kpm_kconfig_bool(#key) +#define KPM_KCONFIG_TRISTATE(key) kpm_kconfig_tristate(#key) +#define KPM_KCONFIG_INT(key, out) kpm_kconfig_int(#key, out) +#define KPM_KCONFIG_HEX(key, out) kpm_kconfig_hex(#key, out) + #endif diff --git a/kernel/include/preset.h b/kernel/include/preset.h index 750be351..da6dfa3c 100644 --- a/kernel/include/preset.h +++ b/kernel/include/preset.h @@ -255,7 +255,9 @@ typedef struct _setup_preset_t uint8_t root_superkey[ROOT_SUPER_KEY_HASH_LEN]; int64_t sprintf_offset; int64_t symbol_lookup_anchor_offset; - uint8_t __[SETUP_PRESERVE_LEN - 16]; + int64_t kconfig_offset; // kimg-relative offset of the gzipped CONFIG_IKCONFIG blob, 0 if absent + int64_t kconfig_size; // byte length of the gzipped blob + uint8_t __[SETUP_PRESERVE_LEN - 32]; patch_config_t patch_config; char additional[ADDITIONAL_LEN]; } setup_preset_t; @@ -279,6 +281,8 @@ typedef struct _setup_preset_t #define setup_root_superkey_offset (setup_superkey_offset + SUPER_KEY_LEN) #define setup_sprintf_offset_offset (setup_root_superkey_offset + ROOT_SUPER_KEY_HASH_LEN) #define setup_symbol_lookup_anchor_offset_offset (setup_sprintf_offset_offset + 8) +#define setup_kconfig_offset_offset (setup_symbol_lookup_anchor_offset_offset + 8) +#define setup_kconfig_size_offset (setup_kconfig_offset_offset + 8) #define setup_patch_config_offset (setup_root_superkey_offset + ROOT_SUPER_KEY_HASH_LEN + SETUP_PRESERVE_LEN) #define setup_end (setup_patch_config_offset + PATCH_CONFIG_LEN) #endif diff --git a/kernel/include/puff.h b/kernel/include/puff.h new file mode 100644 index 00000000..e23a2454 --- /dev/null +++ b/kernel/include/puff.h @@ -0,0 +1,35 @@ +/* puff.h + Copyright (C) 2002-2013 Mark Adler, all rights reserved + version 2.3, 21 Jan 2013 + + This software is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Mark Adler madler@alumni.caltech.edu + */ + + +/* + * See puff.c for purpose and usage. + */ +#ifndef NIL +# define NIL ((unsigned char *)0) /* for no output option */ +#endif + +int puff(unsigned char *dest, /* pointer to destination pointer */ + unsigned long *destlen, /* amount of output space */ + const unsigned char *source, /* pointer to source data pointer */ + unsigned long *sourcelen); /* amount of input available */ diff --git a/kernel/include/setjmp.h b/kernel/include/setjmp.h new file mode 100644 index 00000000..fec6ec3f --- /dev/null +++ b/kernel/include/setjmp.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 mhmrdd. All Rights Reserved. + */ + +#ifndef _KP_SETJMP_H_ +#define _KP_SETJMP_H_ + +#include + +// AArch64 callee-saved registers x19-x28, x29 (fp), x30 (lr) and sp give 13 +// slots, padded to 16 for alignment. +typedef uint64_t jmp_buf[16]; + +int setjmp(jmp_buf env) __attribute__((returns_twice)); +void longjmp(jmp_buf env, int val) __attribute__((noreturn)); + +#endif diff --git a/kernel/patch/include/kconfig.h b/kernel/patch/include/kconfig.h index 929fbdd8..3cbebff7 100644 --- a/kernel/patch/include/kconfig.h +++ b/kernel/patch/include/kconfig.h @@ -1,13 +1,22 @@ /* SPDX-License-Identifier: GPL-2.0-or-later */ -/* +/* * Copyright (C) 2023 bmax121. All Rights Reserved. */ #ifndef _KP_KCONFIG_H_ #define _KP_KCONFIG_H_ +#include + // todo: move config to here extern bool has_config_compat; -#endif \ No newline at end of file +extern const void *kp_kconfig_data; +extern unsigned long kp_kconfig_data_size; + +void kpm_kconfig_init(void); +void kpm_kconfig_load_begin(void); +void kpm_kconfig_load_end(void); + +#endif diff --git a/kernel/patch/module/kconfig.c b/kernel/patch/module/kconfig.c new file mode 100644 index 00000000..3779b74a --- /dev/null +++ b/kernel/patch/module/kconfig.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 mhmrdd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "kconfig.h" + +const void *kp_kconfig_data = 0; +unsigned long kp_kconfig_data_size = 0; + +#define KCONFIG_MAX_SIZE (8u << 20) + +struct kconfig_entry +{ + struct list_head list; + char *value; // NULL if absent + char key[]; +}; + +static struct list_head kconfig_cache; +static spinlock_t kconfig_lock; + +// Decompressed .config kept resident only while a load phase is open +// (load_depth > 0), so a burst of lookups shares one decompression. +static char *kconfig_text; +static unsigned long kconfig_text_len; +static int kconfig_load_depth; + +void kpm_kconfig_init(void) +{ + INIT_LIST_HEAD(&kconfig_cache); + spin_lock_init(&kconfig_lock); +} + +void kpm_kconfig_load_begin(void) +{ + spin_lock(&kconfig_lock); + kconfig_load_depth++; + spin_unlock(&kconfig_lock); +} + +void kpm_kconfig_load_end(void) +{ + char *tofree = 0; + spin_lock(&kconfig_lock); + if (kconfig_load_depth > 0 && --kconfig_load_depth == 0) { + tofree = kconfig_text; + kconfig_text = 0; + kconfig_text_len = 0; + } + spin_unlock(&kconfig_lock); + if (tofree) vfree(tofree); +} + +static const unsigned char *gz_deflate_start(const unsigned char *gz, unsigned long gz_len, unsigned long *deflate_len, + unsigned long *isize) +{ + if (gz_len < 18) return 0; + if (gz[0] != 0x1f || gz[1] != 0x8b || gz[2] != 8) return 0; + + unsigned int flg = gz[3]; + unsigned long pos = 10; + + if (flg & 0x04) { // FEXTRA + if (pos + 2 > gz_len) return 0; + unsigned int xlen = gz[pos] | (gz[pos + 1] << 8); + pos += 2 + xlen; + } + if (flg & 0x08) { // FNAME + while (pos < gz_len && gz[pos]) pos++; + pos++; + } + if (flg & 0x10) { // FCOMMENT + while (pos < gz_len && gz[pos]) pos++; + pos++; + } + if (flg & 0x02) pos += 2; // FHCRC + + if (pos + 8 > gz_len) return 0; + + *isize = (unsigned long)gz[gz_len - 4] | ((unsigned long)gz[gz_len - 3] << 8) | + ((unsigned long)gz[gz_len - 2] << 16) | ((unsigned long)gz[gz_len - 1] << 24); + *deflate_len = gz_len - 8 - pos; + return gz + pos; +} + +static char *kconfig_decompress(unsigned long *out_len) +{ + if (!kp_kconfig_data || !kp_kconfig_data_size) return 0; + + unsigned long deflate_len = 0, isize = 0; + const unsigned char *src = + gz_deflate_start((const unsigned char *)kp_kconfig_data, kp_kconfig_data_size, &deflate_len, &isize); + if (!src) { + logkfe("ikconfig: malformed gzip header\n"); + return 0; + } + if (!isize || isize > KCONFIG_MAX_SIZE) { + logkfe("ikconfig: bad uncompressed size %lx\n", isize); + return 0; + } + + char *out = (char *)vmalloc(isize + 1); + if (!out) return 0; + + unsigned long dlen = isize, slen = deflate_len; + int rc = puff((unsigned char *)out, &dlen, src, &slen); + if (rc) { + logkfe("ikconfig: inflate failed %d\n", rc); + vfree(out); + return 0; + } + + out[dlen] = '\0'; + *out_len = dlen; + return out; +} + +static int kconfig_find(const char *text, unsigned long text_len, const char *key, unsigned long klen, const char **val, + unsigned long *val_len) +{ + const char *p = text; + const char *end = text + text_len; + + while (p < end) { + const char *nl = (const char *)lib_memchr(p, '\n', end - p); + const char *line_end = nl ? nl : end; + + if ((unsigned long)(line_end - p) > klen && !lib_strncmp(p, key, klen) && p[klen] == '=') { + *val = p + klen + 1; + *val_len = line_end - (p + klen + 1); + return 1; + } + p = nl ? nl + 1 : end; + } + return 0; +} + +// both cache helpers run under kconfig_lock +static struct kconfig_entry *cache_find(const char *key) +{ + struct kconfig_entry *e; + list_for_each_entry(e, &kconfig_cache, list) + { + if (!lib_strcmp(e->key, key)) return e; + } + return 0; +} + +static struct kconfig_entry *cache_add(const char *key, unsigned long klen, const char *val, unsigned long vlen) +{ + unsigned long need = sizeof(struct kconfig_entry) + klen + 1 + (val ? vlen + 1 : 0); + struct kconfig_entry *e = (struct kconfig_entry *)kp_malloc(need); + if (!e) return 0; + + lib_memcpy(e->key, key, klen); + e->key[klen] = '\0'; + if (val) { + e->value = e->key + klen + 1; + if (vlen) lib_memcpy(e->value, val, vlen); + e->value[vlen] = '\0'; + } else { + e->value = 0; + } + list_add_tail(&e->list, &kconfig_cache); + return e; +} + +const char *kpm_kconfig_get(const char *key) +{ + if (!key || !key[0]) return 0; + if (!kp_kconfig_data || !kp_kconfig_data_size) return 0; + + struct kconfig_entry *e; + + spin_lock(&kconfig_lock); + e = cache_find(key); + spin_unlock(&kconfig_lock); + if (e) return e->value; + + // hold the window open for this lookup so a shared text cannot be freed + // under us, and so distinct keys within one phase share one decompression + kpm_kconfig_load_begin(); + + const char *text; + unsigned long tlen; + char *local = 0; + + spin_lock(&kconfig_lock); + text = kconfig_text; + tlen = kconfig_text_len; + spin_unlock(&kconfig_lock); + + if (!text) { + local = kconfig_decompress(&tlen); + spin_lock(&kconfig_lock); + if (kconfig_text) { + text = kconfig_text; + tlen = kconfig_text_len; + } else if (local) { + kconfig_text = local; + kconfig_text_len = tlen; + text = local; + local = 0; + } + spin_unlock(&kconfig_lock); + } + + const char *val = 0; + unsigned long vlen = 0; + if (text && kconfig_find(text, tlen, key, lib_strlen(key), &val, &vlen)) { + if (vlen >= 2 && val[0] == '"' && val[vlen - 1] == '"') { + val++; + vlen -= 2; + } + } else { + val = 0; + } + + unsigned long klen = lib_strlen(key); + spin_lock(&kconfig_lock); + e = cache_find(key); + if (!e && text) e = cache_add(key, klen, val, vlen); // text NULL means decompress failed, allow retry + spin_unlock(&kconfig_lock); + + if (local) vfree(local); + kpm_kconfig_load_end(); + + return e ? e->value : 0; +} +KP_EXPORT_SYMBOL(kpm_kconfig_get); diff --git a/kernel/patch/module/module.c b/kernel/patch/module/module.c index 73cb8e5f..434b3ca8 100644 --- a/kernel/patch/module/module.c +++ b/kernel/patch/module/module.c @@ -25,6 +25,7 @@ #include "module.h" #include "relo.h" +#include "kconfig.h" #define SZ_128M 0x08000000 @@ -432,7 +433,9 @@ long load_module(const void *data, int len, const char *args, const char *event, flush_icache_all(); + kpm_kconfig_load_begin(); rc = (*mod->init)(mod->args, event, reserved); + kpm_kconfig_load_end(); if (!rc) { logkfi("[%s] succeed with [%s] \n", mod->info.name, args); @@ -662,4 +665,5 @@ void module_init() { INIT_LIST_HEAD(&modules.list); spin_lock_init(&module_lock); + kpm_kconfig_init(); } diff --git a/kernel/patch/patch.c b/kernel/patch/patch.c index 4b5bfcea..96aa5db6 100644 --- a/kernel/patch/patch.c +++ b/kernel/patch/patch.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -123,7 +124,9 @@ void extra_event_init(const char *event) { if (!event) return; log_boot("event: %s\n", event); + kpm_kconfig_load_begin(); on_each_extra_item(extra_event_load_kpm, (void *)event); + kpm_kconfig_load_end(); } KP_EXPORT_SYMBOL(extra_event_init); diff --git a/kpms/demo-kconfig/Makefile b/kpms/demo-kconfig/Makefile new file mode 100644 index 00000000..e942edd5 --- /dev/null +++ b/kpms/demo-kconfig/Makefile @@ -0,0 +1,30 @@ +ifndef TARGET_COMPILE + $(error TARGET_COMPILE not set) +endif + +ifndef KP_DIR + KP_DIR = ../.. +endif + + +CC = $(TARGET_COMPILE)gcc +LD = $(TARGET_COMPILE)ld + +INCLUDE_DIRS := . include patch/include linux/include linux/arch/arm64/include linux/tools/arch/arm64/include + +INCLUDE_FLAGS := $(foreach dir,$(INCLUDE_DIRS),-I$(KP_DIR)/kernel/$(dir)) + +objs := kconfig.o + +all: kconfig.kpm + +kconfig.kpm: ${objs} + ${CC} -r -o $@ $^ + +%.o: %.c + ${CC} $(CFLAGS) $(INCLUDE_FLAGS) -Tkconfig.lds -c -O2 -o $@ $< + +.PHONY: clean +clean: + rm -rf *.kpm + find . -name "*.o" | xargs rm -f diff --git a/kpms/demo-kconfig/kconfig.c b/kpms/demo-kconfig/kconfig.c new file mode 100644 index 00000000..3476d76e --- /dev/null +++ b/kpms/demo-kconfig/kconfig.c @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2026 mhmrdd. All Rights Reserved. + */ + +#include +#include +#include +#include +#include +#include + +KPM_NAME("kpm-kconfig-demo"); +KPM_VERSION("1.0.0"); +KPM_LICENSE("GPL v2"); +KPM_AUTHOR("mhmrdd"); +KPM_DESCRIPTION("KernelPatch Module kernel config resolution example"); + +static void dump_kconfig(const char *tag) +{ + const char *lv = KPM_KCONFIG_GET(CONFIG_LOCALVERSION); + long hz = 0; + + pr_info("kpm kconfig [%s] CONFIG_KALLSYMS bool=%d tristate=%d\n", tag, KPM_KCONFIG_BOOL(CONFIG_KALLSYMS), + KPM_KCONFIG_TRISTATE(CONFIG_KALLSYMS)); + if (KPM_KCONFIG_INT(CONFIG_HZ, &hz)) pr_info("kpm kconfig [%s] CONFIG_HZ=%ld\n", tag, hz); + pr_info("kpm kconfig [%s] CONFIG_LOCALVERSION=%s\n", tag, lv ? lv : "(unset)"); +} + +static long kconfig_demo_init(const char *args, const char *event, void *__user reserved) +{ + pr_info("kpm kconfig-demo init, event: %s, args: %s\n", event, args); + dump_kconfig("init"); + return 0; +} + +static long kconfig_demo_control0(const char *args, char *__user out_msg, int outlen) +{ + dump_kconfig("ctl0"); + return 0; +} + +static long kconfig_demo_exit(void *__user reserved) +{ + dump_kconfig("exit"); + pr_info("kpm kconfig-demo exit\n"); + return 0; +} + +KPM_INIT(kconfig_demo_init); +KPM_CTL0(kconfig_demo_control0); +KPM_EXIT(kconfig_demo_exit); diff --git a/kpms/demo-kconfig/kconfig.lds b/kpms/demo-kconfig/kconfig.lds new file mode 100644 index 00000000..22e36a21 --- /dev/null +++ b/kpms/demo-kconfig/kconfig.lds @@ -0,0 +1,5 @@ +SECTIONS { + .plt (NOLOAD) : { BYTE(0) } + .init.plt (NOLOAD) : { BYTE(0) } + .text.ftrace_trampoline (NOLOAD) : { BYTE(0) } +} diff --git a/tools/kallsym.c b/tools/kallsym.c index 03d38a53..41928bee 100644 --- a/tools/kallsym.c +++ b/tools/kallsym.c @@ -1132,6 +1132,30 @@ int dump_all_ikconfig(char *img, int32_t imglen) return 0; } +// Locate the gzipped CONFIG_IKCONFIG blob. On success *offset points at the +// gzip stream (right after IKCFG_ST, at the 1f 8b magic) relative to img, and +// *size is the gzip byte length up to the IKCFG_ED marker. Returns 0 on +// success, -1 when the kernel was built without CONFIG_IKCONFIG. +int find_ikconfig(const char *img, int32_t imglen, int32_t *offset, int32_t *size) +{ + const char *st = (const char *)memmem(img, imglen, IKCFG_ST, strlen(IKCFG_ST)); + if (!st) return -1; + + const char *gz = st + strlen(IKCFG_ST); + const char *imgend = img + imglen; + // validate the gzip magic and deflate method, guards against a stray marker + if (imgend - gz < 3 || (uint8_t)gz[0] != 0x1f || (uint8_t)gz[1] != 0x8b || (uint8_t)gz[2] != 0x08) { + return -1; + } + + const char *ed = (const char *)memmem(gz, imgend - gz, IKCFG_ED, strlen(IKCFG_ED)); + if (!ed) return -1; + + *offset = (int32_t)(gz - img); + *size = (int32_t)(ed - gz); + return 0; +} + int on_each_symbol(kallsym_t *info, char *img, void *userdata, int32_t (*fn)(int32_t index, char type, const char *symbol, int32_t offset, void *userdata)) { diff --git a/tools/kallsym.h b/tools/kallsym.h index 7f8a84e4..0b740da9 100644 --- a/tools/kallsym.h +++ b/tools/kallsym.h @@ -121,6 +121,7 @@ int find_linux_banner(kallsym_t *info, char *img, int32_t imglen, void *opt); int analyze_kallsym_info(kallsym_t *info, char *img, int32_t imglen, enum arch_type arch, int32_t is_64); int dump_all_symbols(kallsym_t *info, char *img); int dump_all_ikconfig(char *img, int32_t imglen); +int find_ikconfig(const char *img, int32_t imglen, int32_t *offset, int32_t *size); int is_symbol_exists(kallsym_t *info, char *img, const char *symbol); int get_symbol_index_offset(kallsym_t *info, char *img, int32_t index); int get_symbol_offset_and_size(kallsym_t *info, char *img, char *symbol, int32_t *size); diff --git a/tools/patch.c b/tools/patch.c index 1d0877ab..85ca72d3 100644 --- a/tools/patch.c +++ b/tools/patch.c @@ -613,6 +613,18 @@ int patch_update_img(const char *kimg_path, const char *kpimg_path, const char * if (!setup->printk_offset) setup->printk_offset = get_symbol_offset_zero(&kallsym, kallsym_kimg, "_printk"); if (!setup->printk_offset) tools_loge_exit("no symbol printk\n"); + // kernel config (CONFIG_IKCONFIG) location, given to the engine for KPM config resolution + int32_t kcfg_off = 0, kcfg_size = 0; + if (!find_ikconfig(kallsym_kimg, ori_kimg_len, &kcfg_off, &kcfg_size)) { + setup->kconfig_offset = kcfg_off; + setup->kconfig_size = kcfg_size; + tools_logi("ikconfig: offset 0x%x, size 0x%x\n", kcfg_off, kcfg_size); + } else { + setup->kconfig_offset = 0; + setup->kconfig_size = 0; + tools_logw("no CONFIG_IKCONFIG found, KPM kernel config resolution will be disabled\n"); + } + if ((is_be() ^ kinfo->is_be)) { setup->kimg_size = i64swp(setup->kimg_size); setup->kernel_size = i64swp(setup->kernel_size); @@ -627,6 +639,8 @@ int patch_update_img(const char *kimg_path, const char *kpimg_path, const char * setup->symbol_lookup_anchor_offset = i64swp(setup->symbol_lookup_anchor_offset); setup->paging_init_offset = i64swp(setup->paging_init_offset); setup->printk_offset = i64swp(setup->printk_offset); + setup->kconfig_offset = i64swp(setup->kconfig_offset); + setup->kconfig_size = i64swp(setup->kconfig_size); } // map symbol