blob: 6310af5c02d34854395fcdb99305f6661d6f9517 [file] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* Data objects whose checksums must move for reasons the raw bytes do not
* show.
*
* checksum_update_object() hashes a data symbol's length and its bytes, and
* then walks its relocations: a reference into a string section contributes
* the string's *contents*, and any other reference contributes the target
* symbol's name and the adjusted addend. So three changes that leave the
* object's own bytes identical still have to change its checksum:
*
* Each variant is selected by a -D on the patched build only, so the original
* is always the baseline:
*
* WHICH_FUNC the function pointer points somewhere else
* WHICH_STR the string pointer points at a different literal
* STR_CONTENT the string it points at is edited in place
* WHICH_SLOT the same array, at a different index: addend only
* WHICH_PRIV likewise, but a static, reached through its section symbol
*
* The last is the interesting one. Nothing in the pointer changes -- same
* section, same offset -- so a checksum that hashed only the relocation and
* not what it referred to would call the object unchanged, and the patched
* kernel would keep the old string.
*/
static const char __modinfo[]
__attribute__((section(".modinfo"), used, aligned(1))) = "\0name=vmlinux";
int callee_a(int x);
int callee_b(int x);
int callee_a(int x) { return x + 1; }
int callee_b(int x) { return x + 2; }
/*
* String *literals*, not named arrays. The contents-hashing path keys on
* SHF_STRINGS, which the compiler sets on the mergeable .rodata.str1.1 a
* literal lands in and not on a named char[] given a section of its own. A
* fixture using the latter exercises the ordinary name-and-addend path and
* reports nothing when the text changes.
*/
#if defined(PATCHED) && defined(STR_CONTENT)
#define MESSAGE "edited"
#else
#define MESSAGE "original"
#endif
/* A plain data object: only its own bytes decide the checksum. */
#if defined(PATCHED) && defined(PLAIN_VALUE)
int plain = 43;
#else
int plain = 42;
#endif
/*
* A .bss object, where length is the only thing there is to hash: the section
* has no data, so the bytes are skipped and only sym->len distinguishes this
* from an object of another size. An initialised array would not isolate it
* -- growing one changes the hashed bytes as well.
*/
#if defined(PATCHED) && defined(LONGER)
char sized[4];
#else
char sized[2];
#endif
/*
* A reference into the middle of an array: same target symbol, different
* addend. Nothing else in the object changes, so this is the only way to see
* whether the addend is hashed at all.
*/
int slots[4];
/*
* A file-local array. A reference to a static lands on its section symbol
* plus an offset, so the hash has to resolve that back to the underlying
* object before it has a name to hash at all -- a different code path from the
* global above, and one that silently contributes nothing when it fails.
*/
static int priv_slots[4];
struct desc {
int (*fn)(int arg);
const char *str;
int *slot;
int *priv;
};
const struct desc descriptor = {
#if defined(PATCHED) && defined(WHICH_FUNC)
.fn = callee_b,
#else
.fn = callee_a,
#endif
#if defined(PATCHED) && defined(WHICH_STR)
.str = "a different literal",
#else
.str = MESSAGE,
#endif
#if defined(PATCHED) && defined(WHICH_SLOT)
.slot = &slots[2],
#else
.slot = &slots[1],
#endif
#if defined(PATCHED) && defined(WHICH_PRIV)
.priv = &priv_slots[3],
#else
.priv = &priv_slots[1],
#endif
};
int target(int x)
{
return descriptor.fn(x) + plain + sized[0] + (int)descriptor.str[0] +
*descriptor.slot + *descriptor.priv;
}