fontconfig/src/fccache.c

1839 lines
42 KiB
C
Raw Normal View History

2002-02-15 00:34:13 +01:00
/*
2004-12-07 02:14:46 +01:00
* Copyright © 2000 Keith Packard
* Copyright © 2005 Patrick Lam
2002-02-15 00:34:13 +01:00
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of the author(s) not be used in
2002-02-15 00:34:13 +01:00
* advertising or publicity pertaining to distribution of the software without
* specific, written prior permission. The authors make no
2002-02-15 00:34:13 +01:00
* representations about the suitability of this software for any purpose. It
* is provided "as is" without express or implied warranty.
*
* THE AUTHOR(S) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
2002-02-15 00:34:13 +01:00
* INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
* EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY SPECIAL, INDIRECT OR
2002-02-15 00:34:13 +01:00
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
* DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
* TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "fcint.h"
#include "fcarch.h"
#include "fcmd5.h"
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
#include <string.h>
Properly validate offsets in cache files. The cache files are insufficiently validated. Even though the magic number at the beginning of the file as well as time stamps are checked, it is not verified if contained offsets are in legal ranges or are even pointers. The lack of validation allows an attacker to trigger arbitrary free() calls, which in turn allows double free attacks and therefore arbitrary code execution. Due to the conversion from offsets into pointers through macros, this even allows to circumvent ASLR protections. This attack vector allows privilege escalation when used with setuid binaries like fbterm. A user can create ~/.fonts or any other system-defined user-private font directory, run fc-cache and adjust cache files in ~/.cache/fontconfig. The execution of setuid binaries will scan these files and therefore are prone to attacks. If it's not about code execution, an endless loop can be created by letting linked lists become circular linked lists. This patch verifies that: - The file is not larger than the maximum addressable space, which basically only affects 32 bit systems. This allows out of boundary access into unallocated memory. - Offsets are always positive or zero - Offsets do not point outside file boundaries - No pointers are allowed in cache files, every "pointer or offset" field must be an offset or NULL - Iterating linked lists must not take longer than the amount of elements specified. A violation of this rule can break a possible endless loop. If one or more of these points are violated, the cache is recreated. This is current behaviour. Even though this patch fixes many issues, the use of mmap() shall be forbidden in setuid binaries. It is impossible to guarantee with these checks that a malicious user does not change cache files after verification. This should be handled in a different patch. Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2016-06-25 19:18:53 +02:00
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef _WIN32
#include <sys/time.h>
2020-06-25 19:38:48 +02:00
#else
#include <winsock2.h> /* for struct timeval */
#endif
#include <assert.h>
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
# include <unistd.h>
# include <sys/mman.h>
#endif
2015-10-13 06:06:54 +02:00
#if defined(_WIN32)
#include <sys/locking.h>
2017-12-18 13:45:00 +01:00
#endif
2002-02-15 00:34:13 +01:00
#ifndef O_BINARY
#define O_BINARY 0
#endif
FcBool
2017-11-15 08:10:49 +01:00
FcDirCacheCreateUUID (FcChar8 *dir,
FcBool force,
FcConfig *config)
{
Replace UUID file mechanism with per-directory 'map' attribute [v2] The UUID files would be placed in each font directory to provide the unique cache name, independent of path, for that directory. The UUID files are undesireable for a couple of reasons: 1) They must be placed in the font directories to be useful. This requires modifying the font directories themselves, introducing potential visible timestamp changes when running multiple applications, and makes the cache processing inconsistent between applications with permission to write to the font directories and applications without such permission. 2) The UUID contents were generated randomly, which makes the font cache not reproducible across multiple runs. One proposed fix for 2) is to make the UUID dependent on the font directory path, but once we do that, we can simply use the font directory path itself as the key as the original MD5-based font cache naming mechanism did. The goal of the UUID file mechanism was to fix startup time of flatpaks; as the font path names inside the flatpak did not match the font path names in the base system, the font cache would need to be reconstructed the first time the flatpak was launched. The new mechanism for doing this is to allow each '<dir>' element in the configuration include a 'map' attribute. When looking for a cache file for a particular directory, if the directory name starts with the contents of the <dir> element, that portion of the name will be replaced with the value of the 'map' attribute. Outside of the flatpak, nothing need change -- fontconfig will build cache files using real directory names. Inside the flatpak, the custom fonts.conf file will now include mappings such as this: <dir map="/usr/share/fonts">/run/host/fonts</dir> When scanning the directory /run/host/fonts/ttf, fontconfig will use the name /usr/share/fonts/ttf as the source for building the cache file name. The existing FC_FILE replacement code used for the UUID-based implementation continues to correctly adapt font path names seen by applications. v2: Leave FcDirCacheCreateUUID stub around to avoid removing public API function. Document 'map' attribute of <dir> element in fontconfig-user.sgml Suggested-by: Akira TAGOH <akira@tagoh.org> Signed-off-by: Keith Packard <keithp@keithp.com>
2018-10-30 00:39:05 +01:00
return FcTrue;
}
FcBool
FcDirCacheDeleteUUID (const FcChar8 *dir,
FcConfig *config)
{
FcBool ret = FcTrue;
#ifndef _WIN32
const FcChar8 *sysroot;
FcChar8 *target, *d;
struct stat statb;
struct timeval times[2];
config = FcConfigReference (config);
if (!config)
return FcFalse;
sysroot = FcConfigGetSysRoot (config);
if (sysroot)
d = FcStrBuildFilename (sysroot, dir, NULL);
else
d = FcStrBuildFilename (dir, NULL);
if (FcStat (d, &statb) != 0)
{
ret = FcFalse;
goto bail;
}
target = FcStrBuildFilename (d, ".uuid", NULL);
ret = unlink ((char *) target) == 0;
if (ret)
{
times[0].tv_sec = statb.st_atime;
times[1].tv_sec = statb.st_mtime;
#ifdef HAVE_STRUCT_STAT_ST_MTIM
times[0].tv_usec = statb.st_atim.tv_nsec / 1000;
times[1].tv_usec = statb.st_mtim.tv_nsec / 1000;
#else
times[0].tv_usec = 0;
times[1].tv_usec = 0;
#endif
if (utimes ((const char *) d, times) != 0)
{
fprintf (stderr, "Unable to revert mtime: %s\n", d);
}
}
FcStrFree (target);
bail:
FcStrFree (d);
#endif
FcConfigDestroy (config);
return ret;
}
#define CACHEBASE_LEN (1 + 36 + 1 + sizeof (FC_ARCHITECTURE) + sizeof (FC_CACHE_SUFFIX))
static FcBool
FcCacheIsMmapSafe (int fd)
{
2012-10-07 23:42:18 +02:00
enum {
MMAP_NOT_INITIALIZED = 0,
MMAP_USE,
MMAP_DONT_USE,
MMAP_CHECK_FS,
} status;
static void *static_status;
2012-10-07 23:42:18 +02:00
status = (intptr_t) fc_atomic_ptr_get (&static_status);
2012-10-07 23:42:18 +02:00
if (status == MMAP_NOT_INITIALIZED)
{
const char *env = getenv ("FONTCONFIG_USE_MMAP");
FcBool use;
if (env && FcNameBool ((const FcChar8 *) env, &use))
status = use ? MMAP_USE : MMAP_DONT_USE;
else
status = MMAP_CHECK_FS;
(void) fc_atomic_ptr_cmpexch (&static_status, NULL, (void *) (intptr_t) status);
}
2012-10-07 23:42:18 +02:00
if (status == MMAP_CHECK_FS)
return FcIsFsMmapSafe (fd);
else
return status == MMAP_USE;
}
static const char bin2hex[] = { '0', '1', '2', '3',
'4', '5', '6', '7',
'8', '9', 'a', 'b',
'c', 'd', 'e', 'f' };
static FcChar8 *
Replace UUID file mechanism with per-directory 'map' attribute [v2] The UUID files would be placed in each font directory to provide the unique cache name, independent of path, for that directory. The UUID files are undesireable for a couple of reasons: 1) They must be placed in the font directories to be useful. This requires modifying the font directories themselves, introducing potential visible timestamp changes when running multiple applications, and makes the cache processing inconsistent between applications with permission to write to the font directories and applications without such permission. 2) The UUID contents were generated randomly, which makes the font cache not reproducible across multiple runs. One proposed fix for 2) is to make the UUID dependent on the font directory path, but once we do that, we can simply use the font directory path itself as the key as the original MD5-based font cache naming mechanism did. The goal of the UUID file mechanism was to fix startup time of flatpaks; as the font path names inside the flatpak did not match the font path names in the base system, the font cache would need to be reconstructed the first time the flatpak was launched. The new mechanism for doing this is to allow each '<dir>' element in the configuration include a 'map' attribute. When looking for a cache file for a particular directory, if the directory name starts with the contents of the <dir> element, that portion of the name will be replaced with the value of the 'map' attribute. Outside of the flatpak, nothing need change -- fontconfig will build cache files using real directory names. Inside the flatpak, the custom fonts.conf file will now include mappings such as this: <dir map="/usr/share/fonts">/run/host/fonts</dir> When scanning the directory /run/host/fonts/ttf, fontconfig will use the name /usr/share/fonts/ttf as the source for building the cache file name. The existing FC_FILE replacement code used for the UUID-based implementation continues to correctly adapt font path names seen by applications. v2: Leave FcDirCacheCreateUUID stub around to avoid removing public API function. Document 'map' attribute of <dir> element in fontconfig-user.sgml Suggested-by: Akira TAGOH <akira@tagoh.org> Signed-off-by: Keith Packard <keithp@keithp.com>
2018-10-30 00:39:05 +01:00
FcDirCacheBasenameMD5 (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
{
2019-04-04 13:57:13 +02:00
FcChar8 *mapped_dir = NULL;
unsigned char hash[16];
FcChar8 *hex_hash, *key = NULL;
int cnt;
struct MD5Context ctx;
2019-04-02 12:00:17 +02:00
const FcChar8 *salt, *orig_dir = NULL;
salt = FcConfigMapSalt (config, dir);
2019-04-04 13:57:13 +02:00
/* Obtain a path where "dir" is mapped to.
* In case:
* <remap-dir as-path="/usr/share/fonts">/run/host/fonts</remap-dir>
*
* FcConfigMapFontPath (config, "/run/host/fonts") will returns "/usr/share/fonts".
*/
mapped_dir = FcConfigMapFontPath(config, dir);
if (mapped_dir)
2019-04-02 12:00:17 +02:00
{
orig_dir = dir;
2019-04-04 13:57:13 +02:00
dir = mapped_dir;
2019-04-02 12:00:17 +02:00
}
if (salt)
{
size_t dl = strlen ((const char *) dir);
size_t sl = strlen ((const char *) salt);
Replace UUID file mechanism with per-directory 'map' attribute [v2] The UUID files would be placed in each font directory to provide the unique cache name, independent of path, for that directory. The UUID files are undesireable for a couple of reasons: 1) They must be placed in the font directories to be useful. This requires modifying the font directories themselves, introducing potential visible timestamp changes when running multiple applications, and makes the cache processing inconsistent between applications with permission to write to the font directories and applications without such permission. 2) The UUID contents were generated randomly, which makes the font cache not reproducible across multiple runs. One proposed fix for 2) is to make the UUID dependent on the font directory path, but once we do that, we can simply use the font directory path itself as the key as the original MD5-based font cache naming mechanism did. The goal of the UUID file mechanism was to fix startup time of flatpaks; as the font path names inside the flatpak did not match the font path names in the base system, the font cache would need to be reconstructed the first time the flatpak was launched. The new mechanism for doing this is to allow each '<dir>' element in the configuration include a 'map' attribute. When looking for a cache file for a particular directory, if the directory name starts with the contents of the <dir> element, that portion of the name will be replaced with the value of the 'map' attribute. Outside of the flatpak, nothing need change -- fontconfig will build cache files using real directory names. Inside the flatpak, the custom fonts.conf file will now include mappings such as this: <dir map="/usr/share/fonts">/run/host/fonts</dir> When scanning the directory /run/host/fonts/ttf, fontconfig will use the name /usr/share/fonts/ttf as the source for building the cache file name. The existing FC_FILE replacement code used for the UUID-based implementation continues to correctly adapt font path names seen by applications. v2: Leave FcDirCacheCreateUUID stub around to avoid removing public API function. Document 'map' attribute of <dir> element in fontconfig-user.sgml Suggested-by: Akira TAGOH <akira@tagoh.org> Signed-off-by: Keith Packard <keithp@keithp.com>
2018-10-30 00:39:05 +01:00
key = (FcChar8 *) malloc (dl + sl + 1);
memcpy (key, dir, dl);
memcpy (key + dl, salt, sl + 1);
2019-04-02 12:25:46 +02:00
key[dl + sl] = 0;
2019-04-02 12:00:17 +02:00
if (!orig_dir)
orig_dir = dir;
dir = key;
}
MD5Init (&ctx);
MD5Update (&ctx, (const unsigned char *)dir, strlen ((const char *) dir));
MD5Final (hash, &ctx);
if (key)
FcStrFree (key);
Replace UUID file mechanism with per-directory 'map' attribute [v2] The UUID files would be placed in each font directory to provide the unique cache name, independent of path, for that directory. The UUID files are undesireable for a couple of reasons: 1) They must be placed in the font directories to be useful. This requires modifying the font directories themselves, introducing potential visible timestamp changes when running multiple applications, and makes the cache processing inconsistent between applications with permission to write to the font directories and applications without such permission. 2) The UUID contents were generated randomly, which makes the font cache not reproducible across multiple runs. One proposed fix for 2) is to make the UUID dependent on the font directory path, but once we do that, we can simply use the font directory path itself as the key as the original MD5-based font cache naming mechanism did. The goal of the UUID file mechanism was to fix startup time of flatpaks; as the font path names inside the flatpak did not match the font path names in the base system, the font cache would need to be reconstructed the first time the flatpak was launched. The new mechanism for doing this is to allow each '<dir>' element in the configuration include a 'map' attribute. When looking for a cache file for a particular directory, if the directory name starts with the contents of the <dir> element, that portion of the name will be replaced with the value of the 'map' attribute. Outside of the flatpak, nothing need change -- fontconfig will build cache files using real directory names. Inside the flatpak, the custom fonts.conf file will now include mappings such as this: <dir map="/usr/share/fonts">/run/host/fonts</dir> When scanning the directory /run/host/fonts/ttf, fontconfig will use the name /usr/share/fonts/ttf as the source for building the cache file name. The existing FC_FILE replacement code used for the UUID-based implementation continues to correctly adapt font path names seen by applications. v2: Leave FcDirCacheCreateUUID stub around to avoid removing public API function. Document 'map' attribute of <dir> element in fontconfig-user.sgml Suggested-by: Akira TAGOH <akira@tagoh.org> Signed-off-by: Keith Packard <keithp@keithp.com>
2018-10-30 00:39:05 +01:00
cache_base[0] = '/';
hex_hash = cache_base + 1;
for (cnt = 0; cnt < 16; ++cnt)
{
hex_hash[2*cnt ] = bin2hex[hash[cnt] >> 4];
hex_hash[2*cnt+1] = bin2hex[hash[cnt] & 0xf];
}
hex_hash[2*cnt] = 0;
strcat ((char *) cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
2019-04-02 12:00:17 +02:00
if (FcDebug() & FC_DBG_CACHE)
{
2019-04-04 13:57:13 +02:00
printf ("cache: %s (dir: %s%s%s%s%s%s)\n", cache_base, orig_dir ? orig_dir : dir, mapped_dir ? " (mapped to " : "", mapped_dir ? (char *)mapped_dir : "", mapped_dir ? ")" : "", salt ? ", salt: " : "", salt ? (char *)salt : "");
2019-04-02 12:00:17 +02:00
}
2019-04-04 13:57:13 +02:00
if (mapped_dir)
FcStrFree(mapped_dir);
return cache_base;
}
#ifndef _WIN32
static FcChar8 *
FcDirCacheBasenameUUID (FcConfig *config, const FcChar8 *dir, FcChar8 cache_base[CACHEBASE_LEN])
{
FcChar8 *target, *fuuid;
const FcChar8 *sysroot = FcConfigGetSysRoot (config);
int fd;
2019-04-04 13:57:13 +02:00
/* We don't need to apply remapping here. because .uuid was created at that very directory
* to determine the cache name no matter where it was mapped to.
*/
cache_base[0] = 0;
if (sysroot)
target = FcStrBuildFilename (sysroot, dir, NULL);
else
target = FcStrdup (dir);
fuuid = FcStrBuildFilename (target, ".uuid", NULL);
if ((fd = FcOpen ((char *) fuuid, O_RDONLY)) != -1)
{
char suuid[37];
ssize_t len;
memset (suuid, 0, sizeof (suuid));
len = read (fd, suuid, 36);
suuid[36] = 0;
close (fd);
if (len < 0)
goto bail;
cache_base[0] = '/';
strcpy ((char *)&cache_base[1], suuid);
strcat ((char *) cache_base, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX);
if (FcDebug () & FC_DBG_CACHE)
{
2019-04-04 13:57:13 +02:00
printf ("cache fallbacks to: %s (dir: %s)\n", cache_base, dir);
}
}
bail:
FcStrFree (fuuid);
FcStrFree (target);
return cache_base;
}
#endif
FcBool
FcDirCacheUnlink (const FcChar8 *dir, FcConfig *config)
{
FcChar8 *cache_hashed = NULL;
FcChar8 cache_base[CACHEBASE_LEN];
#ifndef _WIN32
FcChar8 uuid_cache_base[CACHEBASE_LEN];
#endif
FcStrList *list;
FcChar8 *cache_dir;
const FcChar8 *sysroot;
FcBool ret = FcTrue;
config = FcConfigReference (config);
if (!config)
return FcFalse;
sysroot = FcConfigGetSysRoot (config);
Replace UUID file mechanism with per-directory 'map' attribute [v2] The UUID files would be placed in each font directory to provide the unique cache name, independent of path, for that directory. The UUID files are undesireable for a couple of reasons: 1) They must be placed in the font directories to be useful. This requires modifying the font directories themselves, introducing potential visible timestamp changes when running multiple applications, and makes the cache processing inconsistent between applications with permission to write to the font directories and applications without such permission. 2) The UUID contents were generated randomly, which makes the font cache not reproducible across multiple runs. One proposed fix for 2) is to make the UUID dependent on the font directory path, but once we do that, we can simply use the font directory path itself as the key as the original MD5-based font cache naming mechanism did. The goal of the UUID file mechanism was to fix startup time of flatpaks; as the font path names inside the flatpak did not match the font path names in the base system, the font cache would need to be reconstructed the first time the flatpak was launched. The new mechanism for doing this is to allow each '<dir>' element in the configuration include a 'map' attribute. When looking for a cache file for a particular directory, if the directory name starts with the contents of the <dir> element, that portion of the name will be replaced with the value of the 'map' attribute. Outside of the flatpak, nothing need change -- fontconfig will build cache files using real directory names. Inside the flatpak, the custom fonts.conf file will now include mappings such as this: <dir map="/usr/share/fonts">/run/host/fonts</dir> When scanning the directory /run/host/fonts/ttf, fontconfig will use the name /usr/share/fonts/ttf as the source for building the cache file name. The existing FC_FILE replacement code used for the UUID-based implementation continues to correctly adapt font path names seen by applications. v2: Leave FcDirCacheCreateUUID stub around to avoid removing public API function. Document 'map' attribute of <dir> element in fontconfig-user.sgml Suggested-by: Akira TAGOH <akira@tagoh.org> Signed-off-by: Keith Packard <keithp@keithp.com>
2018-10-30 00:39:05 +01:00
FcDirCacheBasenameMD5 (config, dir, cache_base);
#ifndef _WIN32
FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
#endif
list = FcStrListCreate (config->cacheDirs);
if (!list)
{
ret = FcFalse;
goto bail;
}
while ((cache_dir = FcStrListNext (list)))
{
if (sysroot)
cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
else
cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
if (!cache_hashed)
break;
(void) unlink ((char *) cache_hashed);
FcStrFree (cache_hashed);
#ifndef _WIN32
if (uuid_cache_base[0] != 0)
{
if (sysroot)
cache_hashed = FcStrBuildFilename (sysroot, cache_dir, uuid_cache_base, NULL);
else
cache_hashed = FcStrBuildFilename (cache_dir, uuid_cache_base, NULL);
if (!cache_hashed)
break;
(void) unlink ((char *) cache_hashed);
FcStrFree (cache_hashed);
}
#endif
}
FcStrListDone (list);
FcDirCacheDeleteUUID (dir, config);
/* return FcFalse if something went wrong */
if (cache_dir)
ret = FcFalse;
bail:
FcConfigDestroy (config);
return ret;
}
static int
FcDirCacheOpenFile (const FcChar8 *cache_file, struct stat *file_stat)
{
int fd;
Workaround for stat() brokenness in Microsoft's C library (bug 8526) Fix a couple of longstanding problems with fontconfig on Windows that manifest themselves especially in GIMP. The root cause to the problems is in Microsoft's incredibly stupid stat() implementation. Basically, stat() returns wrong timestamp fields for files on NTFS filesystems on machines that use automatic DST switching. See for instance http://bugzilla.gnome.org/show_bug.cgi?id=154968 and http://www.codeproject.com/datetime/dstbugs.asp As fccache.c now looks at more fields in the stat struct I fill in them all. I noticed that fstat() is used only on a fd just after opening it, so on Win32 I just call my stat() replacement before opening instead... Implementing a good replacement for fstat() would be harder because the code in fccache.c wants to compare inode numbers. There are no (readily accessible) inode numbers on Win32, so I fake it with the hash of the full file name, in the case as it is on disk. And fstat() doesn't know the full file name, so it would be rather hard to come up with a inode number to identify the file. The patch also adds similar handling for the cache directory as for the fonts directory: If a cachedir element in fonts.conf contains the magic string "WINDOWSTEMPDIR_FONTCONFIG_CACHE" it is replaced at runtime with a path under the machine's (or user's) temp folder as returned by GetTempPath(). I don't want to hardcode any pathnames in a fonts.conf intended to be distributed to end-users, most of which who wouldn't know how to edit it anyway. And requiring an installer to edit it gets complicated.
2007-11-14 01:41:55 +01:00
#ifdef _WIN32
if (FcStat (cache_file, file_stat) < 0)
return -1;
#endif
2012-12-06 12:01:52 +01:00
fd = FcOpen((char *) cache_file, O_RDONLY | O_BINARY);
if (fd < 0)
return fd;
Workaround for stat() brokenness in Microsoft's C library (bug 8526) Fix a couple of longstanding problems with fontconfig on Windows that manifest themselves especially in GIMP. The root cause to the problems is in Microsoft's incredibly stupid stat() implementation. Basically, stat() returns wrong timestamp fields for files on NTFS filesystems on machines that use automatic DST switching. See for instance http://bugzilla.gnome.org/show_bug.cgi?id=154968 and http://www.codeproject.com/datetime/dstbugs.asp As fccache.c now looks at more fields in the stat struct I fill in them all. I noticed that fstat() is used only on a fd just after opening it, so on Win32 I just call my stat() replacement before opening instead... Implementing a good replacement for fstat() would be harder because the code in fccache.c wants to compare inode numbers. There are no (readily accessible) inode numbers on Win32, so I fake it with the hash of the full file name, in the case as it is on disk. And fstat() doesn't know the full file name, so it would be rather hard to come up with a inode number to identify the file. The patch also adds similar handling for the cache directory as for the fonts directory: If a cachedir element in fonts.conf contains the magic string "WINDOWSTEMPDIR_FONTCONFIG_CACHE" it is replaced at runtime with a path under the machine's (or user's) temp folder as returned by GetTempPath(). I don't want to hardcode any pathnames in a fonts.conf intended to be distributed to end-users, most of which who wouldn't know how to edit it anyway. And requiring an installer to edit it gets complicated.
2007-11-14 01:41:55 +01:00
#ifndef _WIN32
if (fstat (fd, file_stat) < 0)
{
close (fd);
return -1;
}
Workaround for stat() brokenness in Microsoft's C library (bug 8526) Fix a couple of longstanding problems with fontconfig on Windows that manifest themselves especially in GIMP. The root cause to the problems is in Microsoft's incredibly stupid stat() implementation. Basically, stat() returns wrong timestamp fields for files on NTFS filesystems on machines that use automatic DST switching. See for instance http://bugzilla.gnome.org/show_bug.cgi?id=154968 and http://www.codeproject.com/datetime/dstbugs.asp As fccache.c now looks at more fields in the stat struct I fill in them all. I noticed that fstat() is used only on a fd just after opening it, so on Win32 I just call my stat() replacement before opening instead... Implementing a good replacement for fstat() would be harder because the code in fccache.c wants to compare inode numbers. There are no (readily accessible) inode numbers on Win32, so I fake it with the hash of the full file name, in the case as it is on disk. And fstat() doesn't know the full file name, so it would be rather hard to come up with a inode number to identify the file. The patch also adds similar handling for the cache directory as for the fonts directory: If a cachedir element in fonts.conf contains the magic string "WINDOWSTEMPDIR_FONTCONFIG_CACHE" it is replaced at runtime with a path under the machine's (or user's) temp folder as returned by GetTempPath(). I don't want to hardcode any pathnames in a fonts.conf intended to be distributed to end-users, most of which who wouldn't know how to edit it anyway. And requiring an installer to edit it gets complicated.
2007-11-14 01:41:55 +01:00
#endif
return fd;
}
2010-04-12 18:18:50 +02:00
/*
* Look for a cache file for the specified dir. Attempt
* to use each one we find, stopping when the callback
* indicates success
*/
static FcBool
2010-04-12 18:18:50 +02:00
FcDirCacheProcess (FcConfig *config, const FcChar8 *dir,
FcBool (*callback) (FcConfig *config, int fd, struct stat *fd_stat,
struct stat *dir_stat, struct timeval *cache_mtime, void *closure),
void *closure, FcChar8 **cache_file_ret)
{
int fd = -1;
FcChar8 cache_base[CACHEBASE_LEN];
FcStrList *list;
FcChar8 *cache_dir, *d;
struct stat file_stat, dir_stat;
FcBool ret = FcFalse;
const FcChar8 *sysroot = FcConfigGetSysRoot (config);
struct timeval latest_mtime = (struct timeval){ 0 };
if (sysroot)
d = FcStrBuildFilename (sysroot, dir, NULL);
else
d = FcStrdup (dir);
if (FcStatChecksum (d, &dir_stat) < 0)
{
FcStrFree (d);
return FcFalse;
}
FcStrFree (d);
Replace UUID file mechanism with per-directory 'map' attribute [v2] The UUID files would be placed in each font directory to provide the unique cache name, independent of path, for that directory. The UUID files are undesireable for a couple of reasons: 1) They must be placed in the font directories to be useful. This requires modifying the font directories themselves, introducing potential visible timestamp changes when running multiple applications, and makes the cache processing inconsistent between applications with permission to write to the font directories and applications without such permission. 2) The UUID contents were generated randomly, which makes the font cache not reproducible across multiple runs. One proposed fix for 2) is to make the UUID dependent on the font directory path, but once we do that, we can simply use the font directory path itself as the key as the original MD5-based font cache naming mechanism did. The goal of the UUID file mechanism was to fix startup time of flatpaks; as the font path names inside the flatpak did not match the font path names in the base system, the font cache would need to be reconstructed the first time the flatpak was launched. The new mechanism for doing this is to allow each '<dir>' element in the configuration include a 'map' attribute. When looking for a cache file for a particular directory, if the directory name starts with the contents of the <dir> element, that portion of the name will be replaced with the value of the 'map' attribute. Outside of the flatpak, nothing need change -- fontconfig will build cache files using real directory names. Inside the flatpak, the custom fonts.conf file will now include mappings such as this: <dir map="/usr/share/fonts">/run/host/fonts</dir> When scanning the directory /run/host/fonts/ttf, fontconfig will use the name /usr/share/fonts/ttf as the source for building the cache file name. The existing FC_FILE replacement code used for the UUID-based implementation continues to correctly adapt font path names seen by applications. v2: Leave FcDirCacheCreateUUID stub around to avoid removing public API function. Document 'map' attribute of <dir> element in fontconfig-user.sgml Suggested-by: Akira TAGOH <akira@tagoh.org> Signed-off-by: Keith Packard <keithp@keithp.com>
2018-10-30 00:39:05 +01:00
FcDirCacheBasenameMD5 (config, dir, cache_base);
list = FcStrListCreate (config->cacheDirs);
if (!list)
return FcFalse;
while ((cache_dir = FcStrListNext (list)))
{
FcChar8 *cache_hashed;
#ifndef _WIN32
FcBool retried = FcFalse;
#endif
if (sysroot)
cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
else
cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
if (!cache_hashed)
break;
#ifndef _WIN32
retry:
#endif
fd = FcDirCacheOpenFile (cache_hashed, &file_stat);
if (fd >= 0) {
ret = (*callback) (config, fd, &file_stat, &dir_stat, &latest_mtime, closure);
close (fd);
if (ret)
{
if (cache_file_ret)
{
if (*cache_file_ret)
FcStrFree (*cache_file_ret);
*cache_file_ret = cache_hashed;
}
else
FcStrFree (cache_hashed);
}
else
FcStrFree (cache_hashed);
}
#ifndef _WIN32
else if (!retried)
{
FcChar8 uuid_cache_base[CACHEBASE_LEN];
retried = FcTrue;
FcDirCacheBasenameUUID (config, dir, uuid_cache_base);
if (uuid_cache_base[0] != 0)
{
FcStrFree (cache_hashed);
if (sysroot)
cache_hashed = FcStrBuildFilename (sysroot, cache_dir, uuid_cache_base, NULL);
else
cache_hashed = FcStrBuildFilename (cache_dir, uuid_cache_base, NULL);
if (!cache_hashed)
break;
goto retry;
}
else
FcStrFree (cache_hashed);
}
#endif
else
FcStrFree (cache_hashed);
}
FcStrListDone (list);
2010-04-12 18:18:50 +02:00
if (closure)
return !!(*((FcCache **)closure) != NULL);
return ret;
}
#define FC_CACHE_MIN_MMAP 1024
/*
* Skip list element, make sure the 'next' pointer is the last thing
* in the structure, it will be allocated large enough to hold all
* of the necessary pointers
*/
typedef struct _FcCacheSkip FcCacheSkip;
struct _FcCacheSkip {
FcCache *cache;
2012-10-08 03:03:58 +02:00
FcRef ref;
intptr_t size;
void *allocated;
dev_t cache_dev;
ino_t cache_ino;
time_t cache_mtime;
long cache_mtime_nano;
FcCacheSkip *next[1];
};
/*
* The head of the skip list; pointers for every possible level
* in the skip list, plus the largest level in the list
*/
#define FC_CACHE_MAX_LEVEL 16
/* Protected by cache_lock below */
static FcCacheSkip *fcCacheChains[FC_CACHE_MAX_LEVEL];
static int fcCacheMaxLevel;
2012-10-09 02:02:05 +02:00
static FcMutex *cache_lock;
static void
lock_cache (void)
{
FcMutex *lock;
retry:
lock = fc_atomic_ptr_get (&cache_lock);
if (!lock) {
lock = (FcMutex *) malloc (sizeof (FcMutex));
FcMutexInit (lock);
if (!fc_atomic_ptr_cmpexch (&cache_lock, NULL, lock)) {
FcMutexFinish (lock);
free (lock);
2012-10-09 02:02:05 +02:00
goto retry;
}
FcMutexLock (lock);
/* Initialize random state */
FcRandom ();
return;
2012-10-09 02:02:05 +02:00
}
FcMutexLock (lock);
}
static void
unlock_cache (void)
{
FcMutex *lock;
lock = fc_atomic_ptr_get (&cache_lock);
FcMutexUnlock (lock);
2012-10-09 02:02:05 +02:00
}
static void
free_lock (void)
{
FcMutex *lock;
lock = fc_atomic_ptr_get (&cache_lock);
if (lock && fc_atomic_ptr_cmpexch (&cache_lock, lock, NULL)) {
FcMutexFinish (lock);
free (lock);
}
}
/*
* Generate a random level number, distributed
* so that each level is 1/4 as likely as the one before
*
* Note that level numbers run 1 <= level <= MAX_LEVEL
*/
static int
random_level (void)
{
/* tricky bit -- each bit is '1' 75% of the time */
long int bits = FcRandom () | FcRandom ();
int level = 0;
while (++level < FC_CACHE_MAX_LEVEL)
{
if (bits & 1)
break;
bits >>= 1;
}
return level;
}
/*
* Insert cache into the list
*/
static FcBool
FcCacheInsert (FcCache *cache, struct stat *cache_stat)
{
FcCacheSkip **update[FC_CACHE_MAX_LEVEL];
FcCacheSkip *s, **next;
int i, level;
lock_cache ();
/*
* Find links along each chain
*/
next = fcCacheChains;
for (i = fcCacheMaxLevel; --i >= 0; )
{
for (; (s = next[i]); next = s->next)
if (s->cache > cache)
break;
update[i] = &next[i];
}
/*
* Create new list element
*/
level = random_level ();
if (level > fcCacheMaxLevel)
{
level = fcCacheMaxLevel + 1;
update[fcCacheMaxLevel] = &fcCacheChains[fcCacheMaxLevel];
fcCacheMaxLevel = level;
}
2010-04-12 18:18:50 +02:00
s = malloc (sizeof (FcCacheSkip) + (level - 1) * sizeof (FcCacheSkip *));
if (!s)
return FcFalse;
s->cache = cache;
s->size = cache->size;
s->allocated = NULL;
2012-10-08 03:03:58 +02:00
FcRefInit (&s->ref, 1);
if (cache_stat)
{
s->cache_dev = cache_stat->st_dev;
s->cache_ino = cache_stat->st_ino;
s->cache_mtime = cache_stat->st_mtime;
#ifdef HAVE_STRUCT_STAT_ST_MTIM
s->cache_mtime_nano = cache_stat->st_mtim.tv_nsec;
#else
s->cache_mtime_nano = 0;
#endif
}
else
{
s->cache_dev = 0;
s->cache_ino = 0;
s->cache_mtime = 0;
s->cache_mtime_nano = 0;
}
2010-04-12 18:18:50 +02:00
/*
* Insert into all fcCacheChains
*/
for (i = 0; i < level; i++)
{
s->next[i] = *update[i];
*update[i] = s;
}
unlock_cache ();
return FcTrue;
}
static FcCacheSkip *
FcCacheFindByAddrUnlocked (void *object)
{
int i;
FcCacheSkip **next = fcCacheChains;
FcCacheSkip *s;
if (!object)
return NULL;
/*
* Walk chain pointers one level at a time
*/
for (i = fcCacheMaxLevel; --i >= 0;)
while (next[i] && (char *) object >= ((char *) next[i]->cache + next[i]->size))
next = next[i]->next;
/*
* Here we are
*/
s = next[0];
if (s && (char *) object < ((char *) s->cache + s->size))
return s;
return NULL;
}
static FcCacheSkip *
FcCacheFindByAddr (void *object)
{
FcCacheSkip *ret;
lock_cache ();
ret = FcCacheFindByAddrUnlocked (object);
unlock_cache ();
return ret;
}
static void
2012-10-16 02:35:03 +02:00
FcCacheRemoveUnlocked (FcCache *cache)
{
FcCacheSkip **update[FC_CACHE_MAX_LEVEL];
FcCacheSkip *s, **next;
int i;
void *allocated;
/*
* Find links along each chain
*/
next = fcCacheChains;
for (i = fcCacheMaxLevel; --i >= 0; )
{
for (; (s = next[i]); next = s->next)
if (s->cache >= cache)
break;
update[i] = &next[i];
}
s = next[0];
for (i = 0; i < fcCacheMaxLevel && *update[i] == s; i++)
*update[i] = s->next[i];
while (fcCacheMaxLevel > 0 && fcCacheChains[fcCacheMaxLevel - 1] == NULL)
fcCacheMaxLevel--;
if (s)
{
allocated = s->allocated;
while (allocated)
{
/* First element in allocated chunk is the free list */
next = *(void **)allocated;
free (allocated);
allocated = next;
}
free (s);
}
}
static FcCache *
FcCacheFindByStat (struct stat *cache_stat)
{
FcCacheSkip *s;
lock_cache ();
for (s = fcCacheChains[0]; s; s = s->next[0])
if (s->cache_dev == cache_stat->st_dev &&
s->cache_ino == cache_stat->st_ino &&
s->cache_mtime == cache_stat->st_mtime)
{
#ifdef HAVE_STRUCT_STAT_ST_MTIM
if (s->cache_mtime_nano != cache_stat->st_mtim.tv_nsec)
continue;
#endif
2012-10-08 03:03:58 +02:00
FcRefInc (&s->ref);
unlock_cache ();
return s->cache;
}
unlock_cache ();
return NULL;
}
static void
2012-10-16 02:35:03 +02:00
FcDirCacheDisposeUnlocked (FcCache *cache)
{
2012-10-16 02:35:03 +02:00
FcCacheRemoveUnlocked (cache);
switch (cache->magic) {
case FC_CACHE_MAGIC_ALLOC:
free (cache);
break;
case FC_CACHE_MAGIC_MMAP:
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
munmap (cache, cache->size);
#elif defined(_WIN32)
UnmapViewOfFile (cache);
#endif
break;
}
}
void
FcCacheObjectReference (void *object)
{
FcCacheSkip *skip = FcCacheFindByAddr (object);
if (skip)
2012-10-08 03:03:58 +02:00
FcRefInc (&skip->ref);
}
void
FcCacheObjectDereference (void *object)
{
2012-10-16 02:35:03 +02:00
FcCacheSkip *skip;
2012-10-16 02:35:03 +02:00
lock_cache ();
skip = FcCacheFindByAddrUnlocked (object);
if (skip)
{
if (FcRefDec (&skip->ref) == 1)
2012-10-16 02:35:03 +02:00
FcDirCacheDisposeUnlocked (skip->cache);
}
2012-10-16 02:35:03 +02:00
unlock_cache ();
}
void *
FcCacheAllocate (FcCache *cache, size_t len)
{
FcCacheSkip *skip;
void *allocated = NULL;
lock_cache ();
skip = FcCacheFindByAddrUnlocked (cache);
if (skip)
{
void *chunk = malloc (sizeof (void *) + len);
if (chunk)
{
/* First element in allocated chunk is the free list */
*(void **)chunk = skip->allocated;
skip->allocated = chunk;
/* Return the rest */
allocated = ((FcChar8 *)chunk) + sizeof (void *);
}
}
unlock_cache ();
return allocated;
}
void
FcCacheFini (void)
{
int i;
if (FcDebug() & FC_DBG_CACHE)
{
for (i = 0; i < FC_CACHE_MAX_LEVEL; i++)
{
if (fcCacheChains[i] != NULL)
{
FcCacheSkip *s = fcCacheChains[i];
fprintf(stderr, "Fontconfig error: not freed %p (dir: %s, refcount %" FC_ATOMIC_INT_FORMAT ")\n", s->cache, FcCacheDir(s->cache), s->ref.count);
}
}
}
2012-10-09 02:02:05 +02:00
free_lock ();
}
static FcBool
FcCacheTimeValid (FcConfig *config, FcCache *cache, struct stat *dir_stat)
{
struct stat dir_static;
FcBool fnano = FcTrue;
if (!dir_stat)
{
const FcChar8 *sysroot = FcConfigGetSysRoot (config);
FcChar8 *d;
if (sysroot)
d = FcStrBuildFilename (sysroot, FcCacheDir (cache), NULL);
else
d = FcStrdup (FcCacheDir (cache));
if (FcStatChecksum (d, &dir_static) < 0)
{
FcStrFree (d);
return FcFalse;
}
FcStrFree (d);
dir_stat = &dir_static;
}
#ifdef HAVE_STRUCT_STAT_ST_MTIM
fnano = (cache->checksum_nano == dir_stat->st_mtim.tv_nsec);
if (FcDebug () & FC_DBG_CACHE)
2015-10-15 08:48:23 +02:00
printf ("FcCacheTimeValid dir \"%s\" cache checksum %d.%ld dir checksum %d.%ld\n",
FcCacheDir (cache), cache->checksum, (long)cache->checksum_nano, (int) dir_stat->st_mtime, dir_stat->st_mtim.tv_nsec);
#else
if (FcDebug () & FC_DBG_CACHE)
printf ("FcCacheTimeValid dir \"%s\" cache checksum %d dir checksum %d\n",
FcCacheDir (cache), cache->checksum, (int) dir_stat->st_mtime);
#endif
return dir_stat->st_mtime == 0 || (cache->checksum == (int) dir_stat->st_mtime && fnano);
}
Properly validate offsets in cache files. The cache files are insufficiently validated. Even though the magic number at the beginning of the file as well as time stamps are checked, it is not verified if contained offsets are in legal ranges or are even pointers. The lack of validation allows an attacker to trigger arbitrary free() calls, which in turn allows double free attacks and therefore arbitrary code execution. Due to the conversion from offsets into pointers through macros, this even allows to circumvent ASLR protections. This attack vector allows privilege escalation when used with setuid binaries like fbterm. A user can create ~/.fonts or any other system-defined user-private font directory, run fc-cache and adjust cache files in ~/.cache/fontconfig. The execution of setuid binaries will scan these files and therefore are prone to attacks. If it's not about code execution, an endless loop can be created by letting linked lists become circular linked lists. This patch verifies that: - The file is not larger than the maximum addressable space, which basically only affects 32 bit systems. This allows out of boundary access into unallocated memory. - Offsets are always positive or zero - Offsets do not point outside file boundaries - No pointers are allowed in cache files, every "pointer or offset" field must be an offset or NULL - Iterating linked lists must not take longer than the amount of elements specified. A violation of this rule can break a possible endless loop. If one or more of these points are violated, the cache is recreated. This is current behaviour. Even though this patch fixes many issues, the use of mmap() shall be forbidden in setuid binaries. It is impossible to guarantee with these checks that a malicious user does not change cache files after verification. This should be handled in a different patch. Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2016-06-25 19:18:53 +02:00
static FcBool
FcCacheOffsetsValid (FcCache *cache)
{
char *base = (char *)cache;
char *end = base + cache->size;
intptr_t *dirs;
FcFontSet *fs;
int i, j;
if (cache->dir < 0 || cache->dir > cache->size - sizeof (intptr_t) ||
memchr (base + cache->dir, '\0', cache->size - cache->dir) == NULL)
return FcFalse;
if (cache->dirs < 0 || cache->dirs >= cache->size ||
cache->dirs_count < 0 ||
cache->dirs_count > (cache->size - cache->dirs) / sizeof (intptr_t))
return FcFalse;
dirs = FcCacheDirs (cache);
if (dirs)
{
for (i = 0; i < cache->dirs_count; i++)
{
FcChar8 *dir;
if (dirs[i] < 0 ||
dirs[i] > end - (char *) dirs - sizeof (intptr_t))
return FcFalse;
dir = FcOffsetToPtr (dirs, dirs[i], FcChar8);
if (memchr (dir, '\0', end - (char *) dir) == NULL)
return FcFalse;
}
}
if (cache->set < 0 || cache->set > cache->size - sizeof (FcFontSet))
return FcFalse;
fs = FcCacheSet (cache);
if (fs)
{
if (fs->nfont > (end - (char *) fs) / sizeof (FcPattern))
return FcFalse;
if (!FcIsEncodedOffset(fs->fonts))
Properly validate offsets in cache files. The cache files are insufficiently validated. Even though the magic number at the beginning of the file as well as time stamps are checked, it is not verified if contained offsets are in legal ranges or are even pointers. The lack of validation allows an attacker to trigger arbitrary free() calls, which in turn allows double free attacks and therefore arbitrary code execution. Due to the conversion from offsets into pointers through macros, this even allows to circumvent ASLR protections. This attack vector allows privilege escalation when used with setuid binaries like fbterm. A user can create ~/.fonts or any other system-defined user-private font directory, run fc-cache and adjust cache files in ~/.cache/fontconfig. The execution of setuid binaries will scan these files and therefore are prone to attacks. If it's not about code execution, an endless loop can be created by letting linked lists become circular linked lists. This patch verifies that: - The file is not larger than the maximum addressable space, which basically only affects 32 bit systems. This allows out of boundary access into unallocated memory. - Offsets are always positive or zero - Offsets do not point outside file boundaries - No pointers are allowed in cache files, every "pointer or offset" field must be an offset or NULL - Iterating linked lists must not take longer than the amount of elements specified. A violation of this rule can break a possible endless loop. If one or more of these points are violated, the cache is recreated. This is current behaviour. Even though this patch fixes many issues, the use of mmap() shall be forbidden in setuid binaries. It is impossible to guarantee with these checks that a malicious user does not change cache files after verification. This should be handled in a different patch. Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2016-06-25 19:18:53 +02:00
return FcFalse;
for (i = 0; i < fs->nfont; i++)
{
FcPattern *font = FcFontSetFont (fs, i);
FcPatternElt *e;
FcValueListPtr l;
char *last_offset;
Properly validate offsets in cache files. The cache files are insufficiently validated. Even though the magic number at the beginning of the file as well as time stamps are checked, it is not verified if contained offsets are in legal ranges or are even pointers. The lack of validation allows an attacker to trigger arbitrary free() calls, which in turn allows double free attacks and therefore arbitrary code execution. Due to the conversion from offsets into pointers through macros, this even allows to circumvent ASLR protections. This attack vector allows privilege escalation when used with setuid binaries like fbterm. A user can create ~/.fonts or any other system-defined user-private font directory, run fc-cache and adjust cache files in ~/.cache/fontconfig. The execution of setuid binaries will scan these files and therefore are prone to attacks. If it's not about code execution, an endless loop can be created by letting linked lists become circular linked lists. This patch verifies that: - The file is not larger than the maximum addressable space, which basically only affects 32 bit systems. This allows out of boundary access into unallocated memory. - Offsets are always positive or zero - Offsets do not point outside file boundaries - No pointers are allowed in cache files, every "pointer or offset" field must be an offset or NULL - Iterating linked lists must not take longer than the amount of elements specified. A violation of this rule can break a possible endless loop. If one or more of these points are violated, the cache is recreated. This is current behaviour. Even though this patch fixes many issues, the use of mmap() shall be forbidden in setuid binaries. It is impossible to guarantee with these checks that a malicious user does not change cache files after verification. This should be handled in a different patch. Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2016-06-25 19:18:53 +02:00
if ((char *) font < base ||
(char *) font > end - sizeof (FcFontSet) ||
font->elts_offset < 0 ||
font->elts_offset > end - (char *) font ||
font->num > (end - (char *) font - font->elts_offset) / sizeof (FcPatternElt) ||
!FcRefIsConst (&font->ref))
Properly validate offsets in cache files. The cache files are insufficiently validated. Even though the magic number at the beginning of the file as well as time stamps are checked, it is not verified if contained offsets are in legal ranges or are even pointers. The lack of validation allows an attacker to trigger arbitrary free() calls, which in turn allows double free attacks and therefore arbitrary code execution. Due to the conversion from offsets into pointers through macros, this even allows to circumvent ASLR protections. This attack vector allows privilege escalation when used with setuid binaries like fbterm. A user can create ~/.fonts or any other system-defined user-private font directory, run fc-cache and adjust cache files in ~/.cache/fontconfig. The execution of setuid binaries will scan these files and therefore are prone to attacks. If it's not about code execution, an endless loop can be created by letting linked lists become circular linked lists. This patch verifies that: - The file is not larger than the maximum addressable space, which basically only affects 32 bit systems. This allows out of boundary access into unallocated memory. - Offsets are always positive or zero - Offsets do not point outside file boundaries - No pointers are allowed in cache files, every "pointer or offset" field must be an offset or NULL - Iterating linked lists must not take longer than the amount of elements specified. A violation of this rule can break a possible endless loop. If one or more of these points are violated, the cache is recreated. This is current behaviour. Even though this patch fixes many issues, the use of mmap() shall be forbidden in setuid binaries. It is impossible to guarantee with these checks that a malicious user does not change cache files after verification. This should be handled in a different patch. Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2016-06-25 19:18:53 +02:00
return FcFalse;
e = FcPatternElts(font);
if (e->values != 0 && !FcIsEncodedOffset(e->values))
return FcFalse;
for (j = 0; j < font->num; j++)
{
last_offset = (char *) font + font->elts_offset;
for (l = FcPatternEltValues(&e[j]); l; l = FcValueListNext(l))
{
if ((char *) l < last_offset || (char *) l > end - sizeof (*l) ||
(l->next != NULL && !FcIsEncodedOffset(l->next)))
return FcFalse;
last_offset = (char *) l + 1;
}
}
Properly validate offsets in cache files. The cache files are insufficiently validated. Even though the magic number at the beginning of the file as well as time stamps are checked, it is not verified if contained offsets are in legal ranges or are even pointers. The lack of validation allows an attacker to trigger arbitrary free() calls, which in turn allows double free attacks and therefore arbitrary code execution. Due to the conversion from offsets into pointers through macros, this even allows to circumvent ASLR protections. This attack vector allows privilege escalation when used with setuid binaries like fbterm. A user can create ~/.fonts or any other system-defined user-private font directory, run fc-cache and adjust cache files in ~/.cache/fontconfig. The execution of setuid binaries will scan these files and therefore are prone to attacks. If it's not about code execution, an endless loop can be created by letting linked lists become circular linked lists. This patch verifies that: - The file is not larger than the maximum addressable space, which basically only affects 32 bit systems. This allows out of boundary access into unallocated memory. - Offsets are always positive or zero - Offsets do not point outside file boundaries - No pointers are allowed in cache files, every "pointer or offset" field must be an offset or NULL - Iterating linked lists must not take longer than the amount of elements specified. A violation of this rule can break a possible endless loop. If one or more of these points are violated, the cache is recreated. This is current behaviour. Even though this patch fixes many issues, the use of mmap() shall be forbidden in setuid binaries. It is impossible to guarantee with these checks that a malicious user does not change cache files after verification. This should be handled in a different patch. Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2016-06-25 19:18:53 +02:00
}
}
return FcTrue;
}
/*
* Map a cache file into memory
*/
static FcCache *
FcDirCacheMapFd (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat)
{
FcCache *cache;
FcBool allocated = FcFalse;
Properly validate offsets in cache files. The cache files are insufficiently validated. Even though the magic number at the beginning of the file as well as time stamps are checked, it is not verified if contained offsets are in legal ranges or are even pointers. The lack of validation allows an attacker to trigger arbitrary free() calls, which in turn allows double free attacks and therefore arbitrary code execution. Due to the conversion from offsets into pointers through macros, this even allows to circumvent ASLR protections. This attack vector allows privilege escalation when used with setuid binaries like fbterm. A user can create ~/.fonts or any other system-defined user-private font directory, run fc-cache and adjust cache files in ~/.cache/fontconfig. The execution of setuid binaries will scan these files and therefore are prone to attacks. If it's not about code execution, an endless loop can be created by letting linked lists become circular linked lists. This patch verifies that: - The file is not larger than the maximum addressable space, which basically only affects 32 bit systems. This allows out of boundary access into unallocated memory. - Offsets are always positive or zero - Offsets do not point outside file boundaries - No pointers are allowed in cache files, every "pointer or offset" field must be an offset or NULL - Iterating linked lists must not take longer than the amount of elements specified. A violation of this rule can break a possible endless loop. If one or more of these points are violated, the cache is recreated. This is current behaviour. Even though this patch fixes many issues, the use of mmap() shall be forbidden in setuid binaries. It is impossible to guarantee with these checks that a malicious user does not change cache files after verification. This should be handled in a different patch. Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2016-06-25 19:18:53 +02:00
if (fd_stat->st_size > INTPTR_MAX ||
fd_stat->st_size < (int) sizeof (FcCache))
return NULL;
cache = FcCacheFindByStat (fd_stat);
if (cache)
{
if (FcCacheTimeValid (config, cache, dir_stat))
return cache;
FcDirCacheUnload (cache);
cache = NULL;
}
/*
* Large cache files are mmap'ed, smaller cache files are read. This
* balances the system cost of mmap against per-process memory usage.
*/
if (FcCacheIsMmapSafe (fd) && fd_stat->st_size >= FC_CACHE_MIN_MMAP)
{
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
cache = mmap (0, fd_stat->st_size, PROT_READ, MAP_SHARED, fd, 0);
#if defined(HAVE_POSIX_FADVISE) && defined(POSIX_FADV_WILLNEED)
posix_fadvise (fd, 0, fd_stat->st_size, POSIX_FADV_WILLNEED);
#endif
if (cache == MAP_FAILED)
cache = NULL;
#elif defined(_WIN32)
{
HANDLE hFileMap;
cache = NULL;
hFileMap = CreateFileMapping((HANDLE) _get_osfhandle(fd), NULL,
PAGE_READONLY, 0, 0, NULL);
if (hFileMap != NULL)
{
2010-04-12 18:18:50 +02:00
cache = MapViewOfFile (hFileMap, FILE_MAP_READ, 0, 0,
fd_stat->st_size);
CloseHandle (hFileMap);
}
}
#endif
}
if (!cache)
{
cache = malloc (fd_stat->st_size);
if (!cache)
return NULL;
if (read (fd, cache, fd_stat->st_size) != fd_stat->st_size)
{
free (cache);
return NULL;
}
allocated = FcTrue;
2010-04-12 18:18:50 +02:00
}
if (cache->magic != FC_CACHE_MAGIC_MMAP ||
cache->version < FC_CACHE_VERSION_NUMBER ||
2012-12-30 04:47:49 +01:00
cache->size != (intptr_t) fd_stat->st_size ||
Properly validate offsets in cache files. The cache files are insufficiently validated. Even though the magic number at the beginning of the file as well as time stamps are checked, it is not verified if contained offsets are in legal ranges or are even pointers. The lack of validation allows an attacker to trigger arbitrary free() calls, which in turn allows double free attacks and therefore arbitrary code execution. Due to the conversion from offsets into pointers through macros, this even allows to circumvent ASLR protections. This attack vector allows privilege escalation when used with setuid binaries like fbterm. A user can create ~/.fonts or any other system-defined user-private font directory, run fc-cache and adjust cache files in ~/.cache/fontconfig. The execution of setuid binaries will scan these files and therefore are prone to attacks. If it's not about code execution, an endless loop can be created by letting linked lists become circular linked lists. This patch verifies that: - The file is not larger than the maximum addressable space, which basically only affects 32 bit systems. This allows out of boundary access into unallocated memory. - Offsets are always positive or zero - Offsets do not point outside file boundaries - No pointers are allowed in cache files, every "pointer or offset" field must be an offset or NULL - Iterating linked lists must not take longer than the amount of elements specified. A violation of this rule can break a possible endless loop. If one or more of these points are violated, the cache is recreated. This is current behaviour. Even though this patch fixes many issues, the use of mmap() shall be forbidden in setuid binaries. It is impossible to guarantee with these checks that a malicious user does not change cache files after verification. This should be handled in a different patch. Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
2016-06-25 19:18:53 +02:00
!FcCacheOffsetsValid (cache) ||
!FcCacheTimeValid (config, cache, dir_stat) ||
!FcCacheInsert (cache, fd_stat))
{
if (allocated)
free (cache);
else
{
#if defined(HAVE_MMAP) || defined(__CYGWIN__)
munmap (cache, fd_stat->st_size);
#elif defined(_WIN32)
UnmapViewOfFile (cache);
#endif
}
return NULL;
}
/* Mark allocated caches so they're freed rather than unmapped */
if (allocated)
cache->magic = FC_CACHE_MAGIC_ALLOC;
return cache;
}
void
FcDirCacheReference (FcCache *cache, int nref)
{
FcCacheSkip *skip = FcCacheFindByAddr (cache);
if (skip)
2012-10-08 03:03:58 +02:00
FcRefAdd (&skip->ref, nref);
}
void
FcDirCacheUnload (FcCache *cache)
{
FcCacheObjectDereference (cache);
}
static FcBool
FcDirCacheMapHelper (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat, struct timeval *latest_cache_mtime, void *closure)
{
FcCache *cache = FcDirCacheMapFd (config, fd, fd_stat, dir_stat);
struct timeval cache_mtime, zero_mtime = { 0, 0}, dir_mtime;
if (!cache)
return FcFalse;
cache_mtime.tv_sec = fd_stat->st_mtime;
dir_mtime.tv_sec = dir_stat->st_mtime;
#ifdef HAVE_STRUCT_STAT_ST_MTIM
cache_mtime.tv_usec = fd_stat->st_mtim.tv_nsec / 1000;
dir_mtime.tv_usec = dir_stat->st_mtim.tv_nsec / 1000;
#else
cache_mtime.tv_usec = 0;
dir_mtime.tv_usec = 0;
#endif
/* special take care of OSTree */
if (!timercmp (&zero_mtime, &dir_mtime, !=))
{
if (!timercmp (&zero_mtime, &cache_mtime, !=))
{
if (*((FcCache **) closure))
FcDirCacheUnload (*((FcCache **) closure));
}
else if (*((FcCache **) closure) && !timercmp (&zero_mtime, latest_cache_mtime, !=))
{
FcDirCacheUnload (cache);
return FcFalse;
}
else if (timercmp (latest_cache_mtime, &cache_mtime, <))
{
if (*((FcCache **) closure))
FcDirCacheUnload (*((FcCache **) closure));
}
}
else if (timercmp (latest_cache_mtime, &cache_mtime, <))
{
if (*((FcCache **) closure))
FcDirCacheUnload (*((FcCache **) closure));
}
else
{
FcDirCacheUnload (cache);
return FcFalse;
}
latest_cache_mtime->tv_sec = cache_mtime.tv_sec;
latest_cache_mtime->tv_usec = cache_mtime.tv_usec;
*((FcCache **) closure) = cache;
return FcTrue;
}
FcCache *
FcDirCacheLoad (const FcChar8 *dir, FcConfig *config, FcChar8 **cache_file)
{
FcCache *cache = NULL;
config = FcConfigReference (config);
if (!config)
return NULL;
if (!FcDirCacheProcess (config, dir,
FcDirCacheMapHelper,
&cache, cache_file))
cache = NULL;
FcConfigDestroy (config);
return cache;
}
FcCache *
FcDirCacheLoadFile (const FcChar8 *cache_file, struct stat *file_stat)
{
int fd;
FcCache *cache = NULL;
struct stat my_file_stat;
FcConfig *config;
if (!file_stat)
file_stat = &my_file_stat;
config = FcConfigReference (NULL);
if (!config)
return NULL;
fd = FcDirCacheOpenFile (cache_file, file_stat);
if (fd >= 0)
{
cache = FcDirCacheMapFd (config, fd, file_stat, NULL);
close (fd);
}
FcConfigDestroy (config);
return cache;
}
static int
FcDirChecksum (struct stat *statb)
{
int ret = (int) statb->st_mtime;
char *endptr;
char *source_date_epoch;
unsigned long long epoch;
source_date_epoch = getenv("SOURCE_DATE_EPOCH");
if (source_date_epoch)
{
errno = 0;
epoch = strtoull(source_date_epoch, &endptr, 10);
if (endptr == source_date_epoch)
fprintf (stderr,
"Fontconfig: SOURCE_DATE_EPOCH invalid\n");
else if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0))
|| (errno != 0 && epoch == 0))
fprintf (stderr,
"Fontconfig: SOURCE_DATE_EPOCH: strtoull: %s: %" FC_UINT64_FORMAT "\n",
strerror(errno), epoch);
else if (*endptr != '\0')
fprintf (stderr,
"Fontconfig: SOURCE_DATE_EPOCH has trailing garbage\n");
else if (epoch > ULONG_MAX)
fprintf (stderr,
"Fontconfig: SOURCE_DATE_EPOCH must be <= %lu but saw: %" FC_UINT64_FORMAT "\n",
ULONG_MAX, epoch);
else if (epoch < ret)
/* Only override if directory is newer */
ret = (int) epoch;
}
return ret;
}
static int64_t
FcDirChecksumNano (struct stat *statb)
{
#ifdef HAVE_STRUCT_STAT_ST_MTIM
/* No nanosecond component to parse */
if (getenv("SOURCE_DATE_EPOCH"))
return 0;
return statb->st_mtim.tv_nsec;
#else
return 0;
#endif
}
/*
* Validate a cache file by reading the header and checking
* the magic number and the size field
*/
static FcBool
FcDirCacheValidateHelper (FcConfig *config, int fd, struct stat *fd_stat, struct stat *dir_stat, struct timeval *latest_cache_mtime, void *closure FC_UNUSED)
{
FcBool ret = FcTrue;
FcCache c;
2010-04-12 18:18:50 +02:00
if (read (fd, &c, sizeof (FcCache)) != sizeof (FcCache))
ret = FcFalse;
else if (c.magic != FC_CACHE_MAGIC_MMAP)
ret = FcFalse;
else if (c.version < FC_CACHE_VERSION_NUMBER)
ret = FcFalse;
else if (fd_stat->st_size != c.size)
ret = FcFalse;
else if (c.checksum != FcDirChecksum (dir_stat))
ret = FcFalse;
#ifdef HAVE_STRUCT_STAT_ST_MTIM
else if (c.checksum_nano != FcDirChecksumNano (dir_stat))
ret = FcFalse;
#endif
return ret;
}
static FcBool
FcDirCacheValidConfig (const FcChar8 *dir, FcConfig *config)
{
2010-04-12 18:18:50 +02:00
return FcDirCacheProcess (config, dir,
FcDirCacheValidateHelper,
NULL, NULL);
}
FcBool
FcDirCacheValid (const FcChar8 *dir)
{
FcConfig *config;
FcBool ret;
2010-04-12 18:18:50 +02:00
config = FcConfigReference (NULL);
if (!config)
return FcFalse;
ret = FcDirCacheValidConfig (dir, config);
FcConfigDestroy (config);
return ret;
}
/*
* Build a cache structure from the given contents
*/
FcCache *
FcDirCacheBuild (FcFontSet *set, const FcChar8 *dir, struct stat *dir_stat, FcStrSet *dirs)
{
FcSerialize *serialize = FcSerializeCreate ();
FcCache *cache;
int i;
FcChar8 *dir_serialize;
intptr_t *dirs_serialize;
FcFontSet *set_serialize;
2010-04-12 18:18:50 +02:00
if (!serialize)
return NULL;
/*
* Space for cache structure
*/
FcSerializeReserve (serialize, sizeof (FcCache));
/*
* Directory name
*/
if (!FcStrSerializeAlloc (serialize, dir))
goto bail1;
/*
* Subdirs
*/
FcSerializeAlloc (serialize, dirs, dirs->num * sizeof (FcChar8 *));
for (i = 0; i < dirs->num; i++)
if (!FcStrSerializeAlloc (serialize, dirs->strs[i]))
goto bail1;
/*
* Patterns
*/
if (!FcFontSetSerializeAlloc (serialize, set))
goto bail1;
2010-04-12 18:18:50 +02:00
/* Serialize layout complete. Now allocate space and fill it */
cache = malloc (serialize->size);
if (!cache)
goto bail1;
/* shut up valgrind */
memset (cache, 0, serialize->size);
serialize->linear = cache;
cache->magic = FC_CACHE_MAGIC_ALLOC;
cache->version = FC_CACHE_VERSION_NUMBER;
cache->size = serialize->size;
cache->checksum = FcDirChecksum (dir_stat);
cache->checksum_nano = FcDirChecksumNano (dir_stat);
/*
* Serialize directory name
*/
dir_serialize = FcStrSerialize (serialize, dir);
if (!dir_serialize)
goto bail2;
cache->dir = FcPtrToOffset (cache, dir_serialize);
2010-04-12 18:18:50 +02:00
/*
* Serialize sub dirs
*/
dirs_serialize = FcSerializePtr (serialize, dirs);
if (!dirs_serialize)
goto bail2;
cache->dirs = FcPtrToOffset (cache, dirs_serialize);
cache->dirs_count = dirs->num;
2010-04-12 18:18:50 +02:00
for (i = 0; i < dirs->num; i++)
{
FcChar8 *d_serialize = FcStrSerialize (serialize, dirs->strs[i]);
if (!d_serialize)
goto bail2;
dirs_serialize[i] = FcPtrToOffset (dirs_serialize, d_serialize);
}
2010-04-12 18:18:50 +02:00
/*
* Serialize font set
*/
set_serialize = FcFontSetSerialize (serialize, set);
if (!set_serialize)
goto bail2;
cache->set = FcPtrToOffset (cache, set_serialize);
FcSerializeDestroy (serialize);
2010-04-12 18:18:50 +02:00
FcCacheInsert (cache, NULL);
return cache;
bail2:
free (cache);
bail1:
FcSerializeDestroy (serialize);
return NULL;
}
FcCache *
FcDirCacheRebuild (FcCache *cache, struct stat *dir_stat, FcStrSet *dirs)
{
FcCache *new;
FcFontSet *set = FcFontSetDeserialize (FcCacheSet (cache));
const FcChar8 *dir = FcCacheDir (cache);
new = FcDirCacheBuild (set, dir, dir_stat, dirs);
FcFontSetDestroy (set);
return new;
}
/* write serialized state to the cache file */
FcBool
FcDirCacheWrite (FcCache *cache, FcConfig *config)
{
FcChar8 *dir = FcCacheDir (cache);
FcChar8 cache_base[CACHEBASE_LEN];
FcChar8 *cache_hashed;
int fd;
2005-09-28 02:23:15 +02:00
FcAtomic *atomic;
FcStrList *list;
FcChar8 *cache_dir = NULL;
FcChar8 *test_dir, *d = NULL;
FcCacheSkip *skip;
struct stat cache_stat;
2012-12-30 04:47:49 +01:00
unsigned int magic;
int written;
const FcChar8 *sysroot = FcConfigGetSysRoot (config);
/*
* Write it to the first directory in the list which is writable
*/
2010-04-12 18:18:50 +02:00
list = FcStrListCreate (config->cacheDirs);
if (!list)
return FcFalse;
while ((test_dir = FcStrListNext (list)))
{
if (d)
FcStrFree (d);
if (sysroot)
d = FcStrBuildFilename (sysroot, test_dir, NULL);
else
d = FcStrCopyFilename (test_dir);
if (access ((char *) d, W_OK) == 0)
{
cache_dir = FcStrCopyFilename (d);
break;
}
else
{
/*
* If the directory doesn't exist, try to create it
*/
if (access ((char *) d, F_OK) == -1) {
if (FcMakeDirectory (d))
{
cache_dir = FcStrCopyFilename (d);
/* Create CACHEDIR.TAG */
FcDirCacheCreateTagFile (d);
break;
}
}
/*
* Otherwise, try making it writable
*/
else if (chmod ((char *) d, 0755) == 0)
{
cache_dir = FcStrCopyFilename (d);
/* Try to create CACHEDIR.TAG too */
FcDirCacheCreateTagFile (d);
break;
}
}
}
if (!test_dir)
fprintf (stderr, "Fontconfig error: No writable cache directories\n");
if (d)
FcStrFree (d);
FcStrListDone (list);
if (!cache_dir)
return FcFalse;
Replace UUID file mechanism with per-directory 'map' attribute [v2] The UUID files would be placed in each font directory to provide the unique cache name, independent of path, for that directory. The UUID files are undesireable for a couple of reasons: 1) They must be placed in the font directories to be useful. This requires modifying the font directories themselves, introducing potential visible timestamp changes when running multiple applications, and makes the cache processing inconsistent between applications with permission to write to the font directories and applications without such permission. 2) The UUID contents were generated randomly, which makes the font cache not reproducible across multiple runs. One proposed fix for 2) is to make the UUID dependent on the font directory path, but once we do that, we can simply use the font directory path itself as the key as the original MD5-based font cache naming mechanism did. The goal of the UUID file mechanism was to fix startup time of flatpaks; as the font path names inside the flatpak did not match the font path names in the base system, the font cache would need to be reconstructed the first time the flatpak was launched. The new mechanism for doing this is to allow each '<dir>' element in the configuration include a 'map' attribute. When looking for a cache file for a particular directory, if the directory name starts with the contents of the <dir> element, that portion of the name will be replaced with the value of the 'map' attribute. Outside of the flatpak, nothing need change -- fontconfig will build cache files using real directory names. Inside the flatpak, the custom fonts.conf file will now include mappings such as this: <dir map="/usr/share/fonts">/run/host/fonts</dir> When scanning the directory /run/host/fonts/ttf, fontconfig will use the name /usr/share/fonts/ttf as the source for building the cache file name. The existing FC_FILE replacement code used for the UUID-based implementation continues to correctly adapt font path names seen by applications. v2: Leave FcDirCacheCreateUUID stub around to avoid removing public API function. Document 'map' attribute of <dir> element in fontconfig-user.sgml Suggested-by: Akira TAGOH <akira@tagoh.org> Signed-off-by: Keith Packard <keithp@keithp.com>
2018-10-30 00:39:05 +01:00
FcDirCacheBasenameMD5 (config, dir, cache_base);
cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
2018-07-19 04:40:31 +02:00
FcStrFree (cache_dir);
if (!cache_hashed)
return FcFalse;
if (FcDebug () & FC_DBG_CACHE)
printf ("FcDirCacheWriteDir dir \"%s\" file \"%s\"\n",
dir, cache_hashed);
atomic = FcAtomicCreate ((FcChar8 *)cache_hashed);
2005-09-28 02:23:15 +02:00
if (!atomic)
goto bail1;
2005-09-28 02:23:15 +02:00
if (!FcAtomicLock (atomic))
goto bail3;
2012-12-06 12:01:52 +01:00
fd = FcOpen((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT | O_BINARY, 0666);
2005-09-28 02:23:15 +02:00
if (fd == -1)
goto bail4;
2010-04-12 18:18:50 +02:00
/* Temporarily switch magic to MMAP while writing to file */
magic = cache->magic;
if (magic != FC_CACHE_MAGIC_MMAP)
cache->magic = FC_CACHE_MAGIC_MMAP;
2010-04-12 18:18:50 +02:00
/*
* Write cache contents to file
*/
written = write (fd, cache, cache->size);
2010-04-12 18:18:50 +02:00
/* Switch magic back */
if (magic != FC_CACHE_MAGIC_MMAP)
cache->magic = magic;
2010-04-12 18:18:50 +02:00
if (written != cache->size)
{
perror ("write cache");
goto bail5;
}
close(fd);
2005-09-28 02:23:15 +02:00
if (!FcAtomicReplaceOrig(atomic))
goto bail4;
/* If the file is small, update the cache chain entry such that the
* new cache file is not read again. If it's large, we don't do that
* such that we reload it, using mmap, which is shared across processes.
*/
if (cache->size < FC_CACHE_MIN_MMAP && FcStat (cache_hashed, &cache_stat))
{
lock_cache ();
if ((skip = FcCacheFindByAddrUnlocked (cache)))
{
skip->cache_dev = cache_stat.st_dev;
skip->cache_ino = cache_stat.st_ino;
skip->cache_mtime = cache_stat.st_mtime;
#ifdef HAVE_STRUCT_STAT_ST_MTIM
skip->cache_mtime_nano = cache_stat.st_mtim.tv_nsec;
#else
skip->cache_mtime_nano = 0;
#endif
}
unlock_cache ();
}
FcStrFree (cache_hashed);
2005-09-28 02:23:15 +02:00
FcAtomicUnlock (atomic);
FcAtomicDestroy (atomic);
return FcTrue;
bail5:
2005-09-28 02:23:15 +02:00
close (fd);
bail4:
2005-09-28 02:23:15 +02:00
FcAtomicUnlock (atomic);
bail3:
2005-09-28 02:23:15 +02:00
FcAtomicDestroy (atomic);
bail1:
FcStrFree (cache_hashed);
return FcFalse;
}
FcBool
FcDirCacheClean (const FcChar8 *cache_dir, FcBool verbose)
{
DIR *d;
struct dirent *ent;
FcChar8 *dir;
FcBool ret = FcTrue;
FcBool remove;
FcCache *cache;
struct stat target_stat;
const FcChar8 *sysroot;
FcConfig *config;
config = FcConfigReference (NULL);
if (!config)
return FcFalse;
/* FIXME: this API needs to support non-current FcConfig */
sysroot = FcConfigGetSysRoot (config);
if (sysroot)
dir = FcStrBuildFilename (sysroot, cache_dir, NULL);
else
dir = FcStrCopyFilename (cache_dir);
if (!dir)
{
fprintf (stderr, "Fontconfig error: %s: out of memory\n", cache_dir);
ret = FcFalse;
goto bail;
}
if (access ((char *) dir, W_OK) != 0)
{
if (verbose || FcDebug () & FC_DBG_CACHE)
printf ("%s: not cleaning %s cache directory\n", dir,
access ((char *) dir, F_OK) == 0 ? "unwritable" : "non-existent");
goto bail0;
}
if (verbose || FcDebug () & FC_DBG_CACHE)
printf ("%s: cleaning cache directory\n", dir);
d = opendir ((char *) dir);
if (!d)
{
perror ((char *) dir);
ret = FcFalse;
goto bail0;
}
while ((ent = readdir (d)))
{
FcChar8 *file_name;
const FcChar8 *target_dir;
if (ent->d_name[0] == '.')
continue;
/* skip cache files for different architectures and */
/* files which are not cache files at all */
if (strlen(ent->d_name) != 32 + strlen ("-" FC_ARCHITECTURE FC_CACHE_SUFFIX) ||
strcmp(ent->d_name + 32, "-" FC_ARCHITECTURE FC_CACHE_SUFFIX))
continue;
file_name = FcStrBuildFilename (dir, (FcChar8 *)ent->d_name, NULL);
if (!file_name)
{
fprintf (stderr, "Fontconfig error: %s: allocation failure\n", dir);
ret = FcFalse;
break;
}
remove = FcFalse;
cache = FcDirCacheLoadFile (file_name, NULL);
if (!cache)
{
if (verbose || FcDebug () & FC_DBG_CACHE)
printf ("%s: invalid cache file: %s\n", dir, ent->d_name);
remove = FcTrue;
}
else
{
FcChar8 *s;
target_dir = FcCacheDir (cache);
if (sysroot)
s = FcStrBuildFilename (sysroot, target_dir, NULL);
else
s = FcStrdup (target_dir);
if (stat ((char *) s, &target_stat) < 0)
{
if (verbose || FcDebug () & FC_DBG_CACHE)
printf ("%s: %s: missing directory: %s \n",
dir, ent->d_name, s);
remove = FcTrue;
}
FcDirCacheUnload (cache);
FcStrFree (s);
}
if (remove)
{
if (unlink ((char *) file_name) < 0)
{
perror ((char *) file_name);
ret = FcFalse;
}
}
FcStrFree (file_name);
}
closedir (d);
bail0:
FcStrFree (dir);
bail:
FcConfigDestroy (config);
return ret;
}
int
FcDirCacheLock (const FcChar8 *dir,
FcConfig *config)
{
FcChar8 *cache_hashed = NULL;
FcChar8 cache_base[CACHEBASE_LEN];
FcStrList *list;
FcChar8 *cache_dir;
const FcChar8 *sysroot = FcConfigGetSysRoot (config);
int fd = -1;
Replace UUID file mechanism with per-directory 'map' attribute [v2] The UUID files would be placed in each font directory to provide the unique cache name, independent of path, for that directory. The UUID files are undesireable for a couple of reasons: 1) They must be placed in the font directories to be useful. This requires modifying the font directories themselves, introducing potential visible timestamp changes when running multiple applications, and makes the cache processing inconsistent between applications with permission to write to the font directories and applications without such permission. 2) The UUID contents were generated randomly, which makes the font cache not reproducible across multiple runs. One proposed fix for 2) is to make the UUID dependent on the font directory path, but once we do that, we can simply use the font directory path itself as the key as the original MD5-based font cache naming mechanism did. The goal of the UUID file mechanism was to fix startup time of flatpaks; as the font path names inside the flatpak did not match the font path names in the base system, the font cache would need to be reconstructed the first time the flatpak was launched. The new mechanism for doing this is to allow each '<dir>' element in the configuration include a 'map' attribute. When looking for a cache file for a particular directory, if the directory name starts with the contents of the <dir> element, that portion of the name will be replaced with the value of the 'map' attribute. Outside of the flatpak, nothing need change -- fontconfig will build cache files using real directory names. Inside the flatpak, the custom fonts.conf file will now include mappings such as this: <dir map="/usr/share/fonts">/run/host/fonts</dir> When scanning the directory /run/host/fonts/ttf, fontconfig will use the name /usr/share/fonts/ttf as the source for building the cache file name. The existing FC_FILE replacement code used for the UUID-based implementation continues to correctly adapt font path names seen by applications. v2: Leave FcDirCacheCreateUUID stub around to avoid removing public API function. Document 'map' attribute of <dir> element in fontconfig-user.sgml Suggested-by: Akira TAGOH <akira@tagoh.org> Signed-off-by: Keith Packard <keithp@keithp.com>
2018-10-30 00:39:05 +01:00
FcDirCacheBasenameMD5 (config, dir, cache_base);
list = FcStrListCreate (config->cacheDirs);
if (!list)
return -1;
while ((cache_dir = FcStrListNext (list)))
{
if (sysroot)
cache_hashed = FcStrBuildFilename (sysroot, cache_dir, cache_base, NULL);
else
cache_hashed = FcStrBuildFilename (cache_dir, cache_base, NULL);
if (!cache_hashed)
break;
fd = FcOpen ((const char *)cache_hashed, O_RDWR);
FcStrFree (cache_hashed);
/* No caches in that directory. simply retry with another one */
if (fd != -1)
{
2015-10-13 06:06:54 +02:00
#if defined(_WIN32)
if (_locking (fd, _LK_LOCK, 1) == -1)
goto bail;
#else
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fl.l_pid = getpid ();
if (fcntl (fd, F_SETLKW, &fl) == -1)
goto bail;
2015-10-13 06:06:54 +02:00
#endif
break;
}
}
2016-05-20 14:30:44 +02:00
FcStrListDone (list);
return fd;
bail:
FcStrListDone (list);
if (fd != -1)
close (fd);
return -1;
}
void
FcDirCacheUnlock (int fd)
{
if (fd != -1)
{
2015-10-13 06:06:54 +02:00
#if defined(_WIN32)
_locking (fd, _LK_UNLCK, 1);
#else
struct flock fl;
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0;
fl.l_pid = getpid ();
fcntl (fd, F_SETLK, &fl);
2015-10-13 06:06:54 +02:00
#endif
close (fd);
}
}
/*
* Hokey little macro trick to permit the definitions of C functions
* with the same name as CPP macros
*/
#define args1(x) (x)
#define args2(x,y) (x,y)
const FcChar8 *
FcCacheDir args1(const FcCache *c)
{
return FcCacheDir (c);
}
FcFontSet *
FcCacheCopySet args1(const FcCache *c)
{
FcFontSet *old = FcCacheSet (c);
FcFontSet *new = FcFontSetCreate ();
int i;
2010-04-12 18:18:50 +02:00
if (!new)
return NULL;
for (i = 0; i < old->nfont; i++)
{
FcPattern *font = FcFontSetFont (old, i);
FcPatternReference (font);
if (!FcFontSetAdd (new, font))
{
FcFontSetDestroy (new);
return NULL;
}
}
return new;
}
const FcChar8 *
FcCacheSubdir args2(const FcCache *c, int i)
{
return FcCacheSubdir (c, i);
}
int
FcCacheNumSubdir args1(const FcCache *c)
{
return c->dirs_count;
}
int
FcCacheNumFont args1(const FcCache *c)
{
return FcCacheSet(c)->nfont;
}
FcBool
FcDirCacheCreateTagFile (const FcChar8 *cache_dir)
{
FcChar8 *cache_tag;
int fd;
FILE *fp;
FcAtomic *atomic;
static const FcChar8 cache_tag_contents[] =
"Signature: 8a477f597d28d172789f06886806bc55\n"
"# This file is a cache directory tag created by fontconfig.\n"
"# For information about cache directory tags, see:\n"
"# http://www.brynosaurus.com/cachedir/\n";
static size_t cache_tag_contents_size = sizeof (cache_tag_contents) - 1;
FcBool ret = FcFalse;
if (!cache_dir)
return FcFalse;
if (access ((char *) cache_dir, W_OK) == 0)
{
/* Create CACHEDIR.TAG */
cache_tag = FcStrBuildFilename (cache_dir, "CACHEDIR.TAG", NULL);
if (!cache_tag)
return FcFalse;
atomic = FcAtomicCreate ((FcChar8 *)cache_tag);
if (!atomic)
goto bail1;
if (!FcAtomicLock (atomic))
goto bail2;
2012-12-06 12:01:52 +01:00
fd = FcOpen((char *)FcAtomicNewFile (atomic), O_RDWR | O_CREAT, 0644);
if (fd == -1)
goto bail3;
fp = fdopen(fd, "wb");
if (fp == NULL)
goto bail3;
fwrite(cache_tag_contents, cache_tag_contents_size, sizeof (FcChar8), fp);
fclose(fp);
if (!FcAtomicReplaceOrig(atomic))
goto bail3;
ret = FcTrue;
bail3:
FcAtomicUnlock (atomic);
bail2:
FcAtomicDestroy (atomic);
bail1:
FcStrFree (cache_tag);
}
if (FcDebug () & FC_DBG_CACHE)
{
if (ret)
printf ("Created CACHEDIR.TAG at %s\n", cache_dir);
else
printf ("Unable to create CACHEDIR.TAG at %s\n", cache_dir);
}
return ret;
}
void
FcCacheCreateTagFile (FcConfig *config)
{
FcChar8 *cache_dir = NULL, *d = NULL;
FcStrList *list;
const FcChar8 *sysroot;
config = FcConfigReference (config);
if (!config)
return;
sysroot = FcConfigGetSysRoot (config);
list = FcConfigGetCacheDirs (config);
if (!list)
goto bail;
while ((cache_dir = FcStrListNext (list)))
{
if (d)
FcStrFree (d);
if (sysroot)
d = FcStrBuildFilename (sysroot, cache_dir, NULL);
else
d = FcStrCopyFilename (cache_dir);
if (FcDirCacheCreateTagFile (d))
break;
}
if (d)
FcStrFree (d);
FcStrListDone (list);
bail:
FcConfigDestroy (config);
}
#define __fccache__
#include "fcaliastail.h"
#undef __fccache__