]> sourceware.org Git - glibc.git/blame - iconv/gconv_cache.c
syslog: Fix integer overflow in __vsyslog_internal (CVE-2023-6780)
[glibc.git] / iconv / gconv_cache.c
CommitLineData
6b98979f 1/* Cache handling for iconv modules.
dff8da6b 2 Copyright (C) 2001-2024 Free Software Foundation, Inc.
6b98979f 3 This file is part of the GNU C Library.
6b98979f
UD
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
59ba27a6 16 License along with the GNU C Library; if not, see
5a82c748 17 <https://www.gnu.org/licenses/>. */
6b98979f
UD
18
19#include <dlfcn.h>
2c008571 20#include <errno.h>
6b98979f
UD
21#include <fcntl.h>
22#include <stdlib.h>
23#include <string.h>
24#include <unistd.h>
25#include <sys/mman.h>
26#include <sys/stat.h>
27
28#include <gconv_int.h>
29#include <iconvconfig.h>
2c008571 30#include <not-cancel.h>
88f4b692 31#include <pointer_guard.h>
6b98979f
UD
32
33#include "../intl/hash-string.h"
34
230491f0 35static void *gconv_cache;
6b98979f
UD
36static size_t cache_size;
37static int cache_malloced;
38
39
230491f0
UD
40void *
41__gconv_get_cache (void)
42{
43 return gconv_cache;
44}
45
46
6b98979f 47int
6b98979f
UD
48__gconv_load_cache (void)
49{
50 int fd;
52a5fe70 51 struct __stat64_t64 st;
6b98979f
UD
52 struct gconvcache_header *header;
53
54 /* We cannot use the cache if the GCONV_PATH environment variable is
55 set. */
56 __gconv_path_envvar = getenv ("GCONV_PATH");
57 if (__gconv_path_envvar != NULL)
58 return -1;
59
60 /* See whether the cache file exists. */
533deafb 61 fd = __open_nocancel (GCONV_MODULES_CACHE, O_RDONLY | O_CLOEXEC, 0);
6b98979f
UD
62 if (__builtin_expect (fd, 0) == -1)
63 /* Not available. */
64 return -1;
65
66 /* Get information about the file. */
52a5fe70 67 if (__glibc_unlikely (__fstat64_time64 (fd, &st) < 0)
6b98979f
UD
68 /* We do not have to start looking at the file if it cannot contain
69 at least the cache header. */
6dd67bd5 70 || (size_t) st.st_size < sizeof (struct gconvcache_header))
6b98979f
UD
71 {
72 close_and_exit:
c181840c 73 __close_nocancel_nostatus (fd);
6b98979f
UD
74 return -1;
75 }
76
77 /* Make the file content available. */
78 cache_size = st.st_size;
79#ifdef _POSIX_MAPPED_FILES
230491f0 80 gconv_cache = __mmap (NULL, cache_size, PROT_READ, MAP_SHARED, fd, 0);
a1ffb40e 81 if (__glibc_unlikely (gconv_cache == MAP_FAILED))
6b98979f
UD
82#endif
83 {
84 size_t already_read;
85
230491f0
UD
86 gconv_cache = malloc (cache_size);
87 if (gconv_cache == NULL)
6b98979f
UD
88 goto close_and_exit;
89
90 already_read = 0;
91 do
92 {
230491f0 93 ssize_t n = __read (fd, (char *) gconv_cache + already_read,
6b98979f
UD
94 cache_size - already_read);
95 if (__builtin_expect (n, 0) == -1)
96 {
230491f0
UD
97 free (gconv_cache);
98 gconv_cache = NULL;
6b98979f
UD
99 goto close_and_exit;
100 }
101
102 already_read += n;
103 }
104 while (already_read < cache_size);
105
106 cache_malloced = 1;
107 }
108
109 /* We don't need the file descriptor anymore. */
c181840c 110 __close_nocancel_nostatus (fd);
6b98979f
UD
111
112 /* Check the consistency. */
230491f0 113 header = (struct gconvcache_header *) gconv_cache;
6b98979f
UD
114 if (__builtin_expect (header->magic, GCONVCACHE_MAGIC) != GCONVCACHE_MAGIC
115 || __builtin_expect (header->string_offset >= cache_size, 0)
116 || __builtin_expect (header->hash_offset >= cache_size, 0)
117 || __builtin_expect (header->hash_size == 0, 0)
118 || __builtin_expect ((header->hash_offset
119 + header->hash_size * sizeof (struct hash_entry))
120 > cache_size, 0)
121 || __builtin_expect (header->module_offset >= cache_size, 0)
122 || __builtin_expect (header->otherconv_offset > cache_size, 0))
123 {
124 if (cache_malloced)
125 {
230491f0 126 free (gconv_cache);
6b98979f
UD
127 cache_malloced = 0;
128 }
129#ifdef _POSIX_MAPPED_FILES
130 else
230491f0 131 __munmap (gconv_cache, cache_size);
6b98979f 132#endif
230491f0 133 gconv_cache = NULL;
6b98979f
UD
134
135 return -1;
136 }
137
138 /* That worked. */
139 return 0;
140}
141
142
143static int
6b98979f
UD
144find_module_idx (const char *str, size_t *idxp)
145{
146 unsigned int idx;
147 unsigned int hval;
148 unsigned int hval2;
149 const struct gconvcache_header *header;
150 const char *strtab;
151 const struct hash_entry *hashtab;
152 unsigned int limit;
153
230491f0
UD
154 header = (const struct gconvcache_header *) gconv_cache;
155 strtab = (char *) gconv_cache + header->string_offset;
156 hashtab = (struct hash_entry *) ((char *) gconv_cache
9a1f71a7 157 + header->hash_offset);
6b98979f 158
dd9423a6 159 hval = __hash_string (str);
6b98979f
UD
160 idx = hval % header->hash_size;
161 hval2 = 1 + hval % (header->hash_size - 2);
162
163 limit = cache_size - header->string_offset;
164 while (hashtab[idx].string_offset != 0)
165 if (hashtab[idx].string_offset < limit
166 && strcmp (str, strtab + hashtab[idx].string_offset) == 0)
167 {
168 *idxp = hashtab[idx].module_idx;
169 return 0;
170 }
171 else
172 if ((idx += hval2) >= header->hash_size)
173 idx -= header->hash_size;
174
175 /* Nothing found. */
176 return -1;
177}
178
179
8e294940 180#ifndef STATIC_GCONV
6b98979f 181static int
6b98979f
UD
182find_module (const char *directory, const char *filename,
183 struct __gconv_step *result)
184{
185 size_t dirlen = strlen (directory);
186 size_t fnamelen = strlen (filename) + 1;
0db59742 187 char fullname[dirlen + fnamelen];
b2e3d177 188 int status = __GCONV_NOCONV;
6b98979f 189
6b98979f
UD
190 memcpy (__mempcpy (fullname, directory, dirlen), filename, fnamelen);
191
192 result->__shlib_handle = __gconv_find_shlib (fullname);
b2e3d177
UD
193 if (result->__shlib_handle != NULL)
194 {
195 status = __GCONV_OK;
196
0db59742 197 result->__modname = NULL;
b2e3d177
UD
198 result->__fct = result->__shlib_handle->fct;
199 result->__init_fct = result->__shlib_handle->init_fct;
200 result->__end_fct = result->__shlib_handle->end_fct;
6b98979f 201
f9ad060c
UD
202 /* These settings can be overridden by the init function. */
203 result->__btowc_fct = NULL;
b2e3d177 204 result->__data = NULL;
f9ad060c
UD
205
206 /* Call the init function. */
1cf1232c 207 __gconv_init_fct init_fct = result->__init_fct;
1cf1232c 208 PTR_DEMANGLE (init_fct);
1cf1232c
PF
209 if (init_fct != NULL)
210 {
aa87e915 211 status = DL_CALL_FCT (init_fct, (result));
1cf1232c 212 PTR_MANGLE (result->__btowc_fct);
aa87e915 213 }
b2e3d177 214 }
6b98979f 215
6b98979f
UD
216 return status;
217}
8e294940 218#endif
6b98979f
UD
219
220
97b0f3d3 221int
97b0f3d3
UD
222__gconv_compare_alias_cache (const char *name1, const char *name2, int *result)
223{
224 size_t name1_idx;
225 size_t name2_idx;
226
230491f0 227 if (gconv_cache == NULL)
97b0f3d3
UD
228 return -1;
229
230 if (find_module_idx (name1, &name1_idx) != 0
231 || find_module_idx (name2, &name2_idx) != 0)
232 *result = strcmp (name1, name2);
233 else
234 *result = (int) (name1_idx - name2_idx);
235
236 return 0;
237}
238
239
6b98979f 240int
6b98979f
UD
241__gconv_lookup_cache (const char *toset, const char *fromset,
242 struct __gconv_step **handle, size_t *nsteps, int flags)
243{
244 const struct gconvcache_header *header;
245 const char *strtab;
246 size_t fromidx;
247 size_t toidx;
248 const struct module_entry *modtab;
249 const struct module_entry *from_module;
250 const struct module_entry *to_module;
251 struct __gconv_step *result;
252
230491f0 253 if (gconv_cache == NULL)
6b98979f
UD
254 /* We have no cache available. */
255 return __GCONV_NODB;
256
230491f0
UD
257 header = (const struct gconvcache_header *) gconv_cache;
258 strtab = (char *) gconv_cache + header->string_offset;
259 modtab = (const struct module_entry *) ((char *) gconv_cache
6b98979f
UD
260 + header->module_offset);
261
262 if (find_module_idx (fromset, &fromidx) != 0
263 || (header->module_offset + (fromidx + 1) * sizeof (struct module_entry)
264 > cache_size))
265 return __GCONV_NOCONV;
266 from_module = &modtab[fromidx];
267
268 if (find_module_idx (toset, &toidx) != 0
269 || (header->module_offset + (toidx + 1) * sizeof (struct module_entry)
270 > cache_size))
d2a630c3 271 return __GCONV_NOCONV;
6b98979f
UD
272 to_module = &modtab[toidx];
273
274 /* Avoid copy-only transformations if the user requests. */
275 if (__builtin_expect (flags & GCONV_AVOID_NOCONV, 0) && fromidx == toidx)
7b503bcc 276 return __GCONV_NULCONV;
6b98979f
UD
277
278 /* If there are special conversions available examine them first. */
b2e3d177
UD
279 if (fromidx != 0 && toidx != 0
280 && __builtin_expect (from_module->extra_offset, 0) != 0)
6b98979f
UD
281 {
282 /* Search through the list to see whether there is a module
283 matching the destination character set. */
284 const struct extra_entry *extra;
285
286 /* Note the -1. This is due to the offset added in iconvconfig.
287 See there for more explanations. */
230491f0 288 extra = (const struct extra_entry *) ((char *) gconv_cache
6b98979f
UD
289 + header->otherconv_offset
290 + from_module->extra_offset - 1);
291 while (extra->module_cnt != 0
292 && extra->module[extra->module_cnt - 1].outname_offset != toidx)
293 extra = (const struct extra_entry *) ((char *) extra
294 + sizeof (struct extra_entry)
295 + (extra->module_cnt
296 * sizeof (struct extra_entry_module)));
297
298 if (extra->module_cnt != 0)
299 {
300 /* Use the extra module. First determine how many steps. */
301 char *fromname;
302 int idx;
303
304 *nsteps = extra->module_cnt;
305 *handle = result =
306 (struct __gconv_step *) malloc (extra->module_cnt
307 * sizeof (struct __gconv_step));
308 if (result == NULL)
309 return __GCONV_NOMEM;
310
311 fromname = (char *) strtab + from_module->canonname_offset;
312 idx = 0;
313 do
314 {
315 result[idx].__from_name = fromname;
316 fromname = result[idx].__to_name =
317 (char *) strtab + modtab[extra->module[idx].outname_offset].canonname_offset;
318
281ebe7c
UD
319 result[idx].__counter = 1;
320 result[idx].__data = NULL;
321
6b98979f
UD
322#ifndef STATIC_GCONV
323 if (strtab[extra->module[idx].dir_offset] != '\0')
324 {
325 /* Load the module, return handle for it. */
326 int res;
327
328 res = find_module (strtab + extra->module[idx].dir_offset,
329 strtab + extra->module[idx].name_offset,
330 &result[idx]);
331 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
332 {
333 /* Something went wrong. */
334 free (result);
335 goto try_internal;
336 }
337 }
338 else
339#endif
340 /* It's a builtin transformation. */
341 __gconv_get_builtin_trans (strtab
342 + extra->module[idx].name_offset,
343 &result[idx]);
344
345 }
346 while (++idx < extra->module_cnt);
347
348 return __GCONV_OK;
349 }
350 }
351
352 try_internal:
353 /* See whether we can convert via the INTERNAL charset. */
b2e3d177 354 if ((fromidx != 0 && __builtin_expect (from_module->fromname_offset, 1) == 0)
ee6a43e5
UD
355 || (toidx != 0 && __builtin_expect (to_module->toname_offset, 1) == 0)
356 || (fromidx == 0 && toidx == 0))
6b98979f
UD
357 /* Not possible. Nothing we can do. */
358 return __GCONV_NOCONV;
359
ee6a43e5 360 /* We will use up to two modules. Always allocate room for two. */
6b98979f
UD
361 result = (struct __gconv_step *) malloc (2 * sizeof (struct __gconv_step));
362 if (result == NULL)
363 return __GCONV_NOMEM;
364
365 *handle = result;
b2e3d177 366 *nsteps = 0;
6b98979f
UD
367
368 /* Generate data structure for conversion to INTERNAL. */
b2e3d177
UD
369 if (fromidx != 0)
370 {
371 result[0].__from_name = (char *) strtab + from_module->canonname_offset;
372 result[0].__to_name = (char *) "INTERNAL";
6b98979f 373
b2e3d177
UD
374 result[0].__counter = 1;
375 result[0].__data = NULL;
281ebe7c 376
6b98979f 377#ifndef STATIC_GCONV
b2e3d177 378 if (strtab[from_module->todir_offset] != '\0')
6b98979f 379 {
b2e3d177
UD
380 /* Load the module, return handle for it. */
381 int res = find_module (strtab + from_module->todir_offset,
382 strtab + from_module->toname_offset,
383 &result[0]);
384 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
385 {
386 /* Something went wrong. */
387 free (result);
388 return res;
389 }
6b98979f 390 }
b2e3d177 391 else
6b98979f 392#endif
b2e3d177
UD
393 /* It's a builtin transformation. */
394 __gconv_get_builtin_trans (strtab + from_module->toname_offset,
395 &result[0]);
396
397 ++*nsteps;
398 }
6b98979f
UD
399
400 /* Generate data structure for conversion from INTERNAL. */
b2e3d177
UD
401 if (toidx != 0)
402 {
403 int idx = *nsteps;
6b98979f 404
b2e3d177
UD
405 result[idx].__from_name = (char *) "INTERNAL";
406 result[idx].__to_name = (char *) strtab + to_module->canonname_offset;
407
408 result[idx].__counter = 1;
409 result[idx].__data = NULL;
281ebe7c 410
6b98979f 411#ifndef STATIC_GCONV
b2e3d177 412 if (strtab[to_module->fromdir_offset] != '\0')
6b98979f 413 {
b2e3d177
UD
414 /* Load the module, return handle for it. */
415 int res = find_module (strtab + to_module->fromdir_offset,
416 strtab + to_module->fromname_offset,
417 &result[idx]);
418 if (__builtin_expect (res, __GCONV_OK) != __GCONV_OK)
419 {
420 /* Something went wrong. */
421 if (idx != 0)
422 __gconv_release_step (&result[0]);
423 free (result);
424 return res;
425 }
6b98979f 426 }
b2e3d177 427 else
6b98979f 428#endif
b2e3d177
UD
429 /* It's a builtin transformation. */
430 __gconv_get_builtin_trans (strtab + to_module->fromname_offset,
431 &result[idx]);
432
433 ++*nsteps;
434 }
6b98979f
UD
435
436 return __GCONV_OK;
437}
438
439
0db59742
UD
440/* Free memory allocated for the transformation record. */
441void
0db59742
UD
442__gconv_release_cache (struct __gconv_step *steps, size_t nsteps)
443{
0bfddfc9
FW
444 if (gconv_cache != NULL)
445 /* The only thing we have to deallocate is the record with the
446 steps. */
0db59742
UD
447 free (steps);
448}
449
450
6b98979f 451/* Free all resources if necessary. */
88677348
AZN
452void
453__gconv_cache_freemem (void)
6b98979f
UD
454{
455 if (cache_malloced)
230491f0 456 free (gconv_cache);
6b98979f 457#ifdef _POSIX_MAPPED_FILES
b33026a8 458 else if (gconv_cache != NULL)
230491f0 459 __munmap (gconv_cache, cache_size);
6b98979f
UD
460#endif
461}
This page took 0.496062 seconds and 5 git commands to generate.