Bug Summary

File:build-scan/../src/journal/journald-rate-limit.c
Warning:line 122, column 32
Use of memory after it is freed

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple x86_64-unknown-linux-gnu -analyze -disable-free -disable-llvm-verifier -discard-value-names -main-file-name journald-rate-limit.c -analyzer-store=region -analyzer-opt-analyze-nested-blocks -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 2 -fhalf-no-semantic-interposition -mframe-pointer=all -relaxed-aliasing -menable-no-infs -menable-no-nans -menable-unsafe-fp-math -fno-signed-zeros -mreassociate -freciprocal-math -fdenormal-fp-math=preserve-sign,preserve-sign -ffp-contract=fast -fno-rounding-math -ffast-math -ffinite-math-only -mconstructor-aliases -munwind-tables -target-cpu x86-64 -tune-cpu generic -fno-split-dwarf-inlining -debugger-tuning=gdb -resource-dir /usr/lib64/clang/12.0.0 -include config.h -I libjournal-core.a.p -I . -I .. -I src/basic -I ../src/basic -I src/shared -I ../src/shared -I src/systemd -I ../src/systemd -I src/journal -I ../src/journal -I src/journal-remote -I ../src/journal-remote -I src/nspawn -I ../src/nspawn -I src/resolve -I ../src/resolve -I src/timesync -I ../src/timesync -I ../src/time-wait-sync -I src/login -I ../src/login -I src/udev -I ../src/udev -I src/libudev -I ../src/libudev -I src/core -I ../src/core -I ../src/libsystemd/sd-bus -I ../src/libsystemd/sd-device -I ../src/libsystemd/sd-hwdb -I ../src/libsystemd/sd-id128 -I ../src/libsystemd/sd-netlink -I ../src/libsystemd/sd-network -I src/libsystemd-network -I ../src/libsystemd-network -D _FILE_OFFSET_BITS=64 -internal-isystem /usr/local/include -internal-isystem /usr/lib64/clang/12.0.0/include -internal-externc-isystem /include -internal-externc-isystem /usr/include -Wwrite-strings -Wno-unused-parameter -Wno-missing-field-initializers -Wno-unused-result -Wno-format-signedness -Wno-error=nonnull -std=gnu99 -fconst-strings -fdebug-compilation-dir /home/mrc0mmand/repos/@redhat-plumbers/systemd-rhel8/build-scan -ferror-limit 19 -fvisibility hidden -stack-protector 2 -fgnuc-version=4.2.1 -fcolor-diagnostics -analyzer-output=html -faddrsig -o /tmp/scan-build-2021-07-16-221226-1465241-1 -x c ../src/journal/journald-rate-limit.c
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include <errno(*__errno_location ()).h>
4#include <string.h>
5
6#include "alloc-util.h"
7#include "hashmap.h"
8#include "journald-rate-limit.h"
9#include "list.h"
10#include "random-util.h"
11#include "string-util.h"
12#include "util.h"
13
14#define POOLS_MAX5 5
15#define BUCKETS_MAX127 127
16#define GROUPS_MAX2047 2047
17
18static const int priority_map[] = {
19 [LOG_EMERG0] = 0,
20 [LOG_ALERT1] = 0,
21 [LOG_CRIT2] = 0,
22 [LOG_ERR3] = 1,
23 [LOG_WARNING4] = 2,
24 [LOG_NOTICE5] = 3,
25 [LOG_INFO6] = 3,
26 [LOG_DEBUG7] = 4
27};
28
29typedef struct JournalRateLimitPool JournalRateLimitPool;
30typedef struct JournalRateLimitGroup JournalRateLimitGroup;
31
32struct JournalRateLimitPool {
33 usec_t begin;
34 unsigned num;
35 unsigned suppressed;
36};
37
38struct JournalRateLimitGroup {
39 JournalRateLimit *parent;
40
41 char *id;
42
43 /* Interval is stored to keep track of when the group expires */
44 usec_t interval;
45
46 JournalRateLimitPool pools[POOLS_MAX5];
47 uint64_t hash;
48
49 LIST_FIELDS(JournalRateLimitGroup, bucket)JournalRateLimitGroup *bucket_next, *bucket_prev;
50 LIST_FIELDS(JournalRateLimitGroup, lru)JournalRateLimitGroup *lru_next, *lru_prev;
51};
52
53struct JournalRateLimit {
54
55 JournalRateLimitGroup* buckets[BUCKETS_MAX127];
56 JournalRateLimitGroup *lru, *lru_tail;
57
58 unsigned n_groups;
59
60 uint8_t hash_key[16];
61};
62
63JournalRateLimit *journal_rate_limit_new(void) {
64 JournalRateLimit *r;
65
66 r = new0(JournalRateLimit, 1)((JournalRateLimit*) calloc((1), sizeof(JournalRateLimit)));
67 if (!r)
68 return NULL((void*)0);
69
70 random_bytes(r->hash_key, sizeof(r->hash_key));
71
72 return r;
73}
74
75static void journal_rate_limit_group_free(JournalRateLimitGroup *g) {
76 assert(g)do { if ((__builtin_expect(!!(!(g)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("g"), "../src/journal/journald-rate-limit.c"
, 76, __PRETTY_FUNCTION__); } while (0)
;
24
Taking false branch
25
Loop condition is false. Exiting loop
77
78 if (g->parent) {
26
Assuming field 'parent' is null
27
Taking false branch
79 assert(g->parent->n_groups > 0)do { if ((__builtin_expect(!!(!(g->parent->n_groups >
0)),0))) log_assert_failed_realm(LOG_REALM_SYSTEMD, ("g->parent->n_groups > 0"
), "../src/journal/journald-rate-limit.c", 79, __PRETTY_FUNCTION__
); } while (0)
;
80
81 if (g->parent->lru_tail == g)
82 g->parent->lru_tail = g->lru_prev;
83
84 LIST_REMOVE(lru, g->parent->lru, g)do { typeof(*(g->parent->lru)) **_head = &(g->parent
->lru), *_item = (g); do { if ((__builtin_expect(!!(!(_item
)),0))) log_assert_failed_realm(LOG_REALM_SYSTEMD, ("_item"),
"../src/journal/journald-rate-limit.c", 84, __PRETTY_FUNCTION__
); } while (0); if (_item->lru_next) _item->lru_next->
lru_prev = _item->lru_prev; if (_item->lru_prev) _item->
lru_prev->lru_next = _item->lru_next; else { do { if ((
__builtin_expect(!!(!(*_head == _item)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("*_head == _item"), "../src/journal/journald-rate-limit.c"
, 84, __PRETTY_FUNCTION__); } while (0); *_head = _item->lru_next
; } _item->lru_next = _item->lru_prev = ((void*)0); } while
(0)
;
85 LIST_REMOVE(bucket, g->parent->buckets[g->hash % BUCKETS_MAX], g)do { typeof(*(g->parent->buckets[g->hash % 127])) **
_head = &(g->parent->buckets[g->hash % 127]), *_item
= (g); do { if ((__builtin_expect(!!(!(_item)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("_item"), "../src/journal/journald-rate-limit.c"
, 85, __PRETTY_FUNCTION__); } while (0); if (_item->bucket_next
) _item->bucket_next->bucket_prev = _item->bucket_prev
; if (_item->bucket_prev) _item->bucket_prev->bucket_next
= _item->bucket_next; else { do { if ((__builtin_expect(!
!(!(*_head == _item)),0))) log_assert_failed_realm(LOG_REALM_SYSTEMD
, ("*_head == _item"), "../src/journal/journald-rate-limit.c"
, 85, __PRETTY_FUNCTION__); } while (0); *_head = _item->bucket_next
; } _item->bucket_next = _item->bucket_prev = ((void*)0
); } while (0)
;
86
87 g->parent->n_groups--;
88 }
89
90 free(g->id);
91 free(g);
28
Memory is released
92}
93
94void journal_rate_limit_free(JournalRateLimit *r) {
95 assert(r)do { if ((__builtin_expect(!!(!(r)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("r"), "../src/journal/journald-rate-limit.c"
, 95, __PRETTY_FUNCTION__); } while (0)
;
96
97 while (r->lru)
98 journal_rate_limit_group_free(r->lru);
99
100 free(r);
101}
102
103_pure___attribute__ ((pure)) static bool_Bool journal_rate_limit_group_expired(JournalRateLimitGroup *g, usec_t ts) {
104 unsigned i;
105
106 assert(g)do { if ((__builtin_expect(!!(!(g)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("g"), "../src/journal/journald-rate-limit.c"
, 106, __PRETTY_FUNCTION__); } while (0)
;
107
108 for (i = 0; i < POOLS_MAX5; i++)
109 if (g->pools[i].begin + g->interval >= ts)
110 return false0;
111
112 return true1;
113}
114
115static void journal_rate_limit_vacuum(JournalRateLimit *r, usec_t ts) {
116 assert(r)do { if ((__builtin_expect(!!(!(r)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("r"), "../src/journal/journald-rate-limit.c"
, 116, __PRETTY_FUNCTION__); } while (0)
;
18
Taking false branch
19
Loop condition is false. Exiting loop
117
118 /* Makes room for at least one new item, but drop all
119 * expored items too. */
120
121 while (r->n_groups
29.1
Field 'n_groups' is < GROUPS_MAX
>= GROUPS_MAX2047
||
20
Assuming field 'n_groups' is < GROUPS_MAX
22
Loop condition is true. Entering loop body
122 (r->lru_tail
29.2
Field 'lru_tail' is non-null
&& journal_rate_limit_group_expired(r->lru_tail, ts)))
21
Assuming field 'lru_tail' is non-null
30
Use of memory after it is freed
123 journal_rate_limit_group_free(r->lru_tail);
23
Calling 'journal_rate_limit_group_free'
29
Returning; memory was released via 1st parameter
124}
125
126static JournalRateLimitGroup* journal_rate_limit_group_new(JournalRateLimit *r, const char *id, usec_t interval, usec_t ts) {
127 JournalRateLimitGroup *g;
128 struct siphash state;
129
130 assert(r)do { if ((__builtin_expect(!!(!(r)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("r"), "../src/journal/journald-rate-limit.c"
, 130, __PRETTY_FUNCTION__); } while (0)
;
9
Taking false branch
10
Loop condition is false. Exiting loop
131 assert(id)do { if ((__builtin_expect(!!(!(id)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("id"), "../src/journal/journald-rate-limit.c"
, 131, __PRETTY_FUNCTION__); } while (0)
;
11
Taking false branch
12
Loop condition is false. Exiting loop
132
133 g = new0(JournalRateLimitGroup, 1)((JournalRateLimitGroup*) calloc((1), sizeof(JournalRateLimitGroup
)))
;
134 if (!g)
13
Assuming 'g' is non-null
14
Taking false branch
135 return NULL((void*)0);
136
137 g->id = strdup(id);
138 if (!g->id)
15
Assuming field 'id' is non-null
16
Taking false branch
139 goto fail;
140
141 siphash24_init(&state, r->hash_key);
142 string_hash_func(g->id, &state);
143 g->hash = siphash24_finalize(&state);
144
145 g->interval = interval;
146
147 journal_rate_limit_vacuum(r, ts);
17
Calling 'journal_rate_limit_vacuum'
148
149 LIST_PREPEND(bucket, r->buckets[g->hash % BUCKETS_MAX], g)do { typeof(*(r->buckets[g->hash % 127])) **_head = &
(r->buckets[g->hash % 127]), *_item = (g); do { if ((__builtin_expect
(!!(!(_item)),0))) log_assert_failed_realm(LOG_REALM_SYSTEMD,
("_item"), "../src/journal/journald-rate-limit.c", 149, __PRETTY_FUNCTION__
); } while (0); if ((_item->bucket_next = *_head)) _item->
bucket_next->bucket_prev = _item; _item->bucket_prev = (
(void*)0); *_head = _item; } while (0)
;
150 LIST_PREPEND(lru, r->lru, g)do { typeof(*(r->lru)) **_head = &(r->lru), *_item =
(g); do { if ((__builtin_expect(!!(!(_item)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("_item"), "../src/journal/journald-rate-limit.c"
, 150, __PRETTY_FUNCTION__); } while (0); if ((_item->lru_next
= *_head)) _item->lru_next->lru_prev = _item; _item->
lru_prev = ((void*)0); *_head = _item; } while (0)
;
151 if (!g->lru_next)
152 r->lru_tail = g;
153 r->n_groups++;
154
155 g->parent = r;
156 return g;
157
158fail:
159 journal_rate_limit_group_free(g);
160 return NULL((void*)0);
161}
162
163static unsigned burst_modulate(unsigned burst, uint64_t available) {
164 unsigned k;
165
166 /* Modulates the burst rate a bit with the amount of available
167 * disk space */
168
169 k = u64log2(available);
170
171 /* 1MB */
172 if (k <= 20)
173 return burst;
174
175 burst = (burst * (k-16)) / 4;
176
177 /*
178 * Example:
179 *
180 * <= 1MB = rate * 1
181 * 16MB = rate * 2
182 * 256MB = rate * 3
183 * 4GB = rate * 4
184 * 64GB = rate * 5
185 * 1TB = rate * 6
186 */
187
188 return burst;
189}
190
191int journal_rate_limit_test(JournalRateLimit *r, const char *id, usec_t rl_interval, unsigned rl_burst, int priority, uint64_t available) {
192 uint64_t h;
193 JournalRateLimitGroup *g;
194 JournalRateLimitPool *p;
195 struct siphash state;
196 unsigned burst;
197 usec_t ts;
198
199 assert(id)do { if ((__builtin_expect(!!(!(id)),0))) log_assert_failed_realm
(LOG_REALM_SYSTEMD, ("id"), "../src/journal/journald-rate-limit.c"
, 199, __PRETTY_FUNCTION__); } while (0)
;
1
Assuming 'id' is non-null
2
Taking false branch
3
Loop condition is false. Exiting loop
200
201 /* Returns:
202 *
203 * 0 → the log message shall be suppressed,
204 * 1 + n → the log message shall be permitted, and n messages were dropped from the peer before
205 * < 0 → error
206 */
207
208 if (!r)
4
Assuming 'r' is non-null
5
Taking false branch
209 return 1;
210
211 ts = now(CLOCK_MONOTONIC1);
212
213 siphash24_init(&state, r->hash_key);
214 string_hash_func(id, &state);
215 h = siphash24_finalize(&state);
216 g = r->buckets[h % BUCKETS_MAX127];
217
218 LIST_FOREACH(bucket, g, g)for ((g) = (g); (g); (g) = (g)->bucket_next)
6
Loop condition is false. Execution continues on line 222
219 if (streq(g->id, id)(strcmp((g->id),(id)) == 0))
220 break;
221
222 if (!g
6.1
'g' is null
) {
7
Taking true branch
223 g = journal_rate_limit_group_new(r, id, rl_interval, ts);
8
Calling 'journal_rate_limit_group_new'
224 if (!g)
225 return -ENOMEM12;
226 } else
227 g->interval = rl_interval;
228
229 if (rl_interval == 0 || rl_burst == 0)
230 return 1;
231
232 burst = burst_modulate(rl_burst, available);
233
234 p = &g->pools[priority_map[priority]];
235
236 if (p->begin <= 0) {
237 p->suppressed = 0;
238 p->num = 1;
239 p->begin = ts;
240 return 1;
241 }
242
243 if (p->begin + rl_interval < ts) {
244 unsigned s;
245
246 s = p->suppressed;
247 p->suppressed = 0;
248 p->num = 1;
249 p->begin = ts;
250
251 return 1 + s;
252 }
253
254 if (p->num < burst) {
255 p->num++;
256 return 1;
257 }
258
259 p->suppressed++;
260 return 0;
261}