1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
|
// 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;
}
|