Commit | Line | Data |
---|---|---|
6b5e654d LF |
1 | Fix CVE-2016-5384 (double-free resulting in arbitrary code execution): |
2 | ||
3 | <https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5384> | |
4 | ||
5 | Copied from upstream code repository: | |
6 | ||
7 | <https://cgit.freedesktop.org/fontconfig/commit/?id=7a4a5bd7897d216f0794ca9dbce0a4a5c9d14940> | |
8 | ||
9 | From 7a4a5bd7897d216f0794ca9dbce0a4a5c9d14940 Mon Sep 17 00:00:00 2001 | |
10 | From: Tobias Stoeckmann <tobias@stoeckmann.org> | |
11 | Date: Sat, 25 Jun 2016 19:18:53 +0200 | |
12 | Subject: Properly validate offsets in cache files. | |
13 | ||
14 | The cache files are insufficiently validated. Even though the magic | |
15 | number at the beginning of the file as well as time stamps are checked, | |
16 | it is not verified if contained offsets are in legal ranges or are | |
17 | even pointers. | |
18 | ||
19 | The lack of validation allows an attacker to trigger arbitrary free() | |
20 | calls, which in turn allows double free attacks and therefore arbitrary | |
21 | code execution. Due to the conversion from offsets into pointers through | |
22 | macros, this even allows to circumvent ASLR protections. | |
23 | ||
24 | This attack vector allows privilege escalation when used with setuid | |
25 | binaries like fbterm. A user can create ~/.fonts or any other | |
26 | system-defined user-private font directory, run fc-cache and adjust | |
27 | cache files in ~/.cache/fontconfig. The execution of setuid binaries will | |
28 | scan these files and therefore are prone to attacks. | |
29 | ||
30 | If it's not about code execution, an endless loop can be created by | |
31 | letting linked lists become circular linked lists. | |
32 | ||
33 | This patch verifies that: | |
34 | ||
35 | - The file is not larger than the maximum addressable space, which | |
36 | basically only affects 32 bit systems. This allows out of boundary | |
37 | access into unallocated memory. | |
38 | - Offsets are always positive or zero | |
39 | - Offsets do not point outside file boundaries | |
40 | - No pointers are allowed in cache files, every "pointer or offset" | |
41 | field must be an offset or NULL | |
42 | - Iterating linked lists must not take longer than the amount of elements | |
43 | specified. A violation of this rule can break a possible endless loop. | |
44 | ||
45 | If one or more of these points are violated, the cache is recreated. | |
46 | This is current behaviour. | |
47 | ||
48 | Even though this patch fixes many issues, the use of mmap() shall be | |
49 | forbidden in setuid binaries. It is impossible to guarantee with these | |
50 | checks that a malicious user does not change cache files after | |
51 | verification. This should be handled in a different patch. | |
52 | ||
53 | Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org> | |
54 | ||
55 | diff --git a/src/fccache.c b/src/fccache.c | |
56 | index 71e8f03..02ec301 100644 | |
57 | --- a/src/fccache.c | |
58 | +++ b/src/fccache.c | |
59 | @@ -27,6 +27,7 @@ | |
60 | #include <fcntl.h> | |
61 | #include <dirent.h> | |
62 | #include <string.h> | |
63 | +#include <limits.h> | |
64 | #include <sys/types.h> | |
65 | #include <sys/stat.h> | |
66 | #include <assert.h> | |
67 | @@ -587,6 +588,82 @@ FcCacheTimeValid (FcConfig *config, FcCache *cache, struct stat *dir_stat) | |
68 | return cache->checksum == (int) dir_stat->st_mtime && fnano; | |
69 | } | |
70 | ||
71 | +static FcBool | |
72 | +FcCacheOffsetsValid (FcCache *cache) | |
73 | +{ | |
74 | + char *base = (char *)cache; | |
75 | + char *end = base + cache->size; | |
76 | + intptr_t *dirs; | |
77 | + FcFontSet *fs; | |
78 | + int i, j; | |
79 | + | |
80 | + if (cache->dir < 0 || cache->dir > cache->size - sizeof (intptr_t) || | |
81 | + memchr (base + cache->dir, '\0', cache->size - cache->dir) == NULL) | |
82 | + return FcFalse; | |
83 | + | |
84 | + if (cache->dirs < 0 || cache->dirs >= cache->size || | |
85 | + cache->dirs_count < 0 || | |
86 | + cache->dirs_count > (cache->size - cache->dirs) / sizeof (intptr_t)) | |
87 | + return FcFalse; | |
88 | + | |
89 | + dirs = FcCacheDirs (cache); | |
90 | + if (dirs) | |
91 | + { | |
92 | + for (i = 0; i < cache->dirs_count; i++) | |
93 | + { | |
94 | + FcChar8 *dir; | |
95 | + | |
96 | + if (dirs[i] < 0 || | |
97 | + dirs[i] > end - (char *) dirs - sizeof (intptr_t)) | |
98 | + return FcFalse; | |
99 | + | |
100 | + dir = FcOffsetToPtr (dirs, dirs[i], FcChar8); | |
101 | + if (memchr (dir, '\0', end - (char *) dir) == NULL) | |
102 | + return FcFalse; | |
103 | + } | |
104 | + } | |
105 | + | |
106 | + if (cache->set < 0 || cache->set > cache->size - sizeof (FcFontSet)) | |
107 | + return FcFalse; | |
108 | + | |
109 | + fs = FcCacheSet (cache); | |
110 | + if (fs) | |
111 | + { | |
112 | + if (fs->nfont > (end - (char *) fs) / sizeof (FcPattern)) | |
113 | + return FcFalse; | |
114 | + | |
115 | + if (fs->fonts != 0 && !FcIsEncodedOffset(fs->fonts)) | |
116 | + return FcFalse; | |
117 | + | |
118 | + for (i = 0; i < fs->nfont; i++) | |
119 | + { | |
120 | + FcPattern *font = FcFontSetFont (fs, i); | |
121 | + FcPatternElt *e; | |
122 | + FcValueListPtr l; | |
123 | + | |
124 | + if ((char *) font < base || | |
125 | + (char *) font > end - sizeof (FcFontSet) || | |
126 | + font->elts_offset < 0 || | |
127 | + font->elts_offset > end - (char *) font || | |
128 | + font->num > (end - (char *) font - font->elts_offset) / sizeof (FcPatternElt)) | |
129 | + return FcFalse; | |
130 | + | |
131 | + | |
132 | + e = FcPatternElts(font); | |
133 | + if (e->values != 0 && !FcIsEncodedOffset(e->values)) | |
134 | + return FcFalse; | |
135 | + | |
136 | + for (j = font->num, l = FcPatternEltValues(e); j >= 0 && l; j--, l = FcValueListNext(l)) | |
137 | + if (l->next != NULL && !FcIsEncodedOffset(l->next)) | |
138 | + break; | |
139 | + if (j < 0) | |
140 | + return FcFalse; | |
141 | + } | |
142 | + } | |
143 | + | |
144 | + return FcTrue; | |
145 | +} | |
146 | + | |
147 | /* | |
148 | * Map a cache file into memory | |
149 | */ | |
150 | @@ -596,7 +673,8 @@ FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *di | |
151 | FcCache *cache; | |
152 | FcBool allocated = FcFalse; | |
153 | ||
154 | - if (fd_stat->st_size < (int) sizeof (FcCache)) | |
155 | + if (fd_stat->st_size > INTPTR_MAX || | |
156 | + fd_stat->st_size < (int) sizeof (FcCache)) | |
157 | return NULL; | |
158 | cache = FcCacheFindByStat (fd_stat); | |
159 | if (cache) | |
160 | @@ -652,6 +730,7 @@ FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *di | |
161 | if (cache->magic != FC_CACHE_MAGIC_MMAP || | |
162 | cache->version < FC_CACHE_VERSION_NUMBER || | |
163 | cache->size != (intptr_t) fd_stat->st_size || | |
164 | + !FcCacheOffsetsValid (cache) || | |
165 | !FcCacheTimeValid (config, cache, dir_stat) || | |
166 | !FcCacheInsert (cache, fd_stat)) | |
167 | { | |
168 | -- | |
169 | cgit v0.10.2 | |
170 |