Files
bind9/lib/isc/win32/file.c
Ondřej Surý 99ba29bc52 Change isc_random() to be just PRNG, and add isc_nonce_buf() that uses CSPRNG
This commit reverts the previous change to use system provided
entropy, as (SYS_)getrandom is very slow on Linux because it is
a syscall.

The change introduced in this commit adds a new call isc_nonce_buf
that uses CSPRNG from cryptographic library provider to generate
secure data that can be and must be used for generating nonces.
Example usage would be DNS cookies.

The isc_random() API has been changed to use fast PRNG that is not
cryptographically secure, but runs entirely in user space.  Two
contestants have been considered xoroshiro family of the functions
by Villa&Blackman and PCG by O'Neill.  After a consideration the
xoshiro128starstar function has been used as uint32_t random number
provider because it is very fast and has good enough properties
for our usage pattern.

The other change introduced in the commit is the more extensive usage
of isc_random_uniform in places where the usage pattern was
isc_random() % n to prevent modulo bias.  For usage patterns where
only 16 or 8 bits are needed (DNS Message ID), the isc_random()
functions has been renamed to isc_random32(), and isc_random16() and
isc_random8() functions have been introduced by &-ing the
isc_random32() output with 0xffff and 0xff.  Please note that the
functions that uses stripped down bit count doesn't pass our
NIST SP 800-22 based random test.
2018-05-29 22:58:21 +02:00

926 lines
19 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <config.h>
#undef rename
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <io.h>
#include <process.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/utime.h>
#include <isc/file.h>
#include <isc/mem.h>
#include <isc/print.h>
#include <isc/random.h>
#include <isc/result.h>
#include <isc/sha2.h>
#include <isc/stat.h>
#include <isc/string.h>
#include <isc/time.h>
#include <isc/util.h>
#include "errno2result.h"
static const char alphnum[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
/*
* Emulate UNIX mkstemp, which returns an open FD to the new file
*
*/
static int
gettemp(char *path, isc_boolean_t binary, int *doopen) {
char *start, *trv;
struct stat sbuf;
int flags = O_CREAT|O_EXCL|O_RDWR;
if (binary)
flags |= _O_BINARY;
trv = strrchr(path, 'X');
trv++;
/* extra X's get set to 0's */
while (*--trv == 'X') {
isc_uint32_t which = isc_random_uniform(sizeof(alphnum) - 1);
*trv = alphnum[which];
}
/*
* check the target directory; if you have six X's and it
* doesn't exist this runs for a *very* long time.
*/
for (start = trv + 1;; --trv) {
if (trv <= path)
break;
if (*trv == '\\') {
*trv = '\0';
if (stat(path, &sbuf))
return (0);
if (!S_ISDIR(sbuf.st_mode)) {
errno = ENOTDIR;
return (0);
}
*trv = '\\';
break;
}
}
for (;;) {
if (doopen) {
if ((*doopen =
open(path, flags, _S_IREAD | _S_IWRITE)) >= 0)
return (1);
if (errno != EEXIST)
return (0);
} else if (stat(path, &sbuf))
return (errno == ENOENT ? 1 : 0);
/* tricky little algorithm for backward compatibility */
for (trv = start;;) {
if (!*trv)
return (0);
if (*trv == 'z')
*trv++ = 'a';
else {
if (isdigit(*trv))
*trv = 'a';
else
++*trv;
break;
}
}
}
/*NOTREACHED*/
}
static int
mkstemp(char *path, isc_boolean_t binary) {
int fd;
return (gettemp(path, binary, &fd) ? fd : -1);
}
/*
* XXXDCL As the API for accessing file statistics undoubtedly gets expanded,
* it might be good to provide a mechanism that allows for the results
* of a previous stat() to be used again without having to do another stat,
* such as perl's mechanism of using "_" in place of a file name to indicate
* that the results of the last stat should be used. But then you get into
* annoying MP issues. BTW, Win32 has stat().
*/
static isc_result_t
file_stats(const char *file, struct stat *stats) {
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(file != NULL);
REQUIRE(stats != NULL);
if (stat(file, stats) != 0)
result = isc__errno2result(errno);
return (result);
}
static isc_result_t
fd_stats(int fd, struct stat *stats) {
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(stats != NULL);
if (fstat(fd, stats) != 0)
result = isc__errno2result(errno);
return (result);
}
isc_result_t
isc_file_getsizefd(int fd, off_t *size) {
isc_result_t result;
struct stat stats;
REQUIRE(size != NULL);
result = fd_stats(fd, &stats);
if (result == ISC_R_SUCCESS)
*size = stats.st_size;
return (result);
}
isc_result_t
isc_file_mode(const char *file, mode_t *modep) {
isc_result_t result;
struct stat stats;
REQUIRE(modep != NULL);
result = file_stats(file, &stats);
if (result == ISC_R_SUCCESS)
*modep = (stats.st_mode & 07777);
return (result);
}
/*
* isc_file_safemovefile is needed to be defined here to ensure that
* any file with the new name is renamed to a backup name and then the
* rename is done. If all goes well then the backup can be deleted,
* otherwise it gets renamed back.
*/
int
isc_file_safemovefile(const char *oldname, const char *newname) {
BOOL filestatus;
char buf[512];
struct stat sbuf;
BOOL exists = FALSE;
int tmpfd;
/*
* Make sure we have something to do
*/
if (stat(oldname, &sbuf) != 0) {
errno = ENOENT;
return (-1);
}
/*
* Rename to a backup the new file if it still exists
*/
if (stat(newname, &sbuf) == 0) {
exists = TRUE;
strlcpy(buf, newname, sizeof(buf));
strlcat(buf, ".XXXXX", sizeof(buf));
tmpfd = mkstemp(buf, ISC_TRUE);
if (tmpfd > 0)
_close(tmpfd);
(void)DeleteFile(buf);
_chmod(newname, _S_IREAD | _S_IWRITE);
filestatus = MoveFile(newname, buf);
}
/* Now rename the file to the new name
*/
_chmod(oldname, _S_IREAD | _S_IWRITE);
filestatus = MoveFile(oldname, newname);
if (filestatus == 0) {
/*
* Try to rename the backup back to the original name
* if the backup got created
*/
if (exists == TRUE) {
filestatus = MoveFile(buf, newname);
if (filestatus == 0)
errno = EACCES;
}
return (-1);
}
/*
* Delete the backup file if it got created
*/
if (exists == TRUE)
(void)DeleteFile(buf);
return (0);
}
isc_result_t
isc_file_getmodtime(const char *file, isc_time_t *time) {
int fh;
REQUIRE(file != NULL);
REQUIRE(time != NULL);
if ((fh = open(file, _O_RDONLY | _O_BINARY)) < 0)
return (isc__errno2result(errno));
if (!GetFileTime((HANDLE) _get_osfhandle(fh),
NULL,
NULL,
&time->absolute))
{
close(fh);
errno = EINVAL;
return (isc__errno2result(errno));
}
close(fh);
return (ISC_R_SUCCESS);
}
isc_result_t
isc_file_getsize(const char *file, off_t *size) {
isc_result_t result;
struct stat stats;
REQUIRE(file != NULL);
REQUIRE(size != NULL);
result = file_stats(file, &stats);
if (result == ISC_R_SUCCESS)
*size = stats.st_size;
return (result);
}
isc_result_t
isc_file_settime(const char *file, isc_time_t *time) {
int fh;
REQUIRE(file != NULL && time != NULL);
if ((fh = open(file, _O_RDWR | _O_BINARY)) < 0)
return (isc__errno2result(errno));
/*
* Set the date via the filedate system call and return. Failing
* this call implies the new file times are not supported by the
* underlying file system.
*/
if (!SetFileTime((HANDLE) _get_osfhandle(fh),
NULL,
&time->absolute,
&time->absolute))
{
close(fh);
errno = EINVAL;
return (isc__errno2result(errno));
}
close(fh);
return (ISC_R_SUCCESS);
}
#undef TEMPLATE
#define TEMPLATE "XXXXXXXXXX.tmp" /* 14 characters. */
isc_result_t
isc_file_mktemplate(const char *path, char *buf, size_t buflen) {
return (isc_file_template(path, TEMPLATE, buf, buflen));
}
isc_result_t
isc_file_template(const char *path, const char *templet, char *buf,
size_t buflen)
{
char *s;
REQUIRE(templet != NULL);
REQUIRE(buf != NULL);
if (path == NULL)
path = "";
s = strrchr(templet, '\\');
if (s != NULL)
templet = s + 1;
s = strrchr(path, '\\');
if (s != NULL) {
size_t prefixlen = s - path + 1;
if ((prefixlen + strlen(templet) + 1) > buflen)
return (ISC_R_NOSPACE);
/* Copy 'prefixlen' bytes and NUL terminate. */
strlcpy(buf, path, ISC_MIN(prefixlen + 1, buflen));
strlcat(buf, templet, buflen);
} else {
if ((strlen(templet) + 1) > buflen)
return (ISC_R_NOSPACE);
strlcpy(buf, templet, buflen);
}
return (ISC_R_SUCCESS);
}
isc_result_t
isc_file_renameunique(const char *file, char *templet) {
int fd;
int res = 0;
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(file != NULL);
REQUIRE(templet != NULL);
fd = mkstemp(templet, ISC_TRUE);
if (fd == -1)
result = isc__errno2result(errno);
else
close(fd);
if (result == ISC_R_SUCCESS) {
res = isc_file_safemovefile(file, templet);
if (res != 0) {
result = isc__errno2result(errno);
(void)unlink(templet);
}
}
return (result);
}
static isc_result_t
openuniquemode(char *templet, int mode, isc_boolean_t binary, FILE **fp) {
int fd;
FILE *f;
isc_result_t result = ISC_R_SUCCESS;
REQUIRE(templet != NULL);
REQUIRE(fp != NULL && *fp == NULL);
/*
* Win32 does not have mkstemp. Using emulation above.
*/
fd = mkstemp(templet, binary);
if (fd == -1)
result = isc__errno2result(errno);
if (result == ISC_R_SUCCESS) {
#if 1
UNUSED(mode);
#else
(void)fchmod(fd, mode);
#endif
f = fdopen(fd, binary ? "wb+" : "w+");
if (f == NULL) {
result = isc__errno2result(errno);
(void)remove(templet);
(void)close(fd);
} else
*fp = f;
}
return (result);
}
isc_result_t
isc_file_openuniqueprivate(char *templet, FILE **fp) {
int mode = _S_IREAD | _S_IWRITE;
return (openuniquemode(templet, mode, ISC_FALSE, fp));
}
isc_result_t
isc_file_openunique(char *templet, FILE **fp) {
int mode = _S_IREAD | _S_IWRITE;
return (openuniquemode(templet, mode, ISC_FALSE, fp));
}
isc_result_t
isc_file_openuniquemode(char *templet, int mode, FILE **fp) {
return (openuniquemode(templet, mode, ISC_FALSE, fp));
}
isc_result_t
isc_file_bopenuniqueprivate(char *templet, FILE **fp) {
int mode = _S_IREAD | _S_IWRITE;
return (openuniquemode(templet, mode, ISC_TRUE, fp));
}
isc_result_t
isc_file_bopenunique(char *templet, FILE **fp) {
int mode = _S_IREAD | _S_IWRITE;
return (openuniquemode(templet, mode, ISC_TRUE, fp));
}
isc_result_t
isc_file_bopenuniquemode(char *templet, int mode, FILE **fp) {
return (openuniquemode(templet, mode, ISC_TRUE, fp));
}
isc_result_t
isc_file_remove(const char *filename) {
int r;
REQUIRE(filename != NULL);
r = unlink(filename);
if (r == 0)
return (ISC_R_SUCCESS);
else
return (isc__errno2result(errno));
}
isc_result_t
isc_file_rename(const char *oldname, const char *newname) {
int r;
REQUIRE(oldname != NULL);
REQUIRE(newname != NULL);
r = isc_file_safemovefile(oldname, newname);
if (r == 0)
return (ISC_R_SUCCESS);
else
return (isc__errno2result(errno));
}
isc_boolean_t
isc_file_exists(const char *pathname) {
struct stat stats;
REQUIRE(pathname != NULL);
return (ISC_TF(file_stats(pathname, &stats) == ISC_R_SUCCESS));
}
isc_result_t
isc_file_isplainfile(const char *filename) {
/*
* This function returns success if filename is a plain file.
*/
struct stat filestat;
memset(&filestat,0,sizeof(struct stat));
if ((stat(filename, &filestat)) == -1)
return(isc__errno2result(errno));
if(! S_ISREG(filestat.st_mode))
return(ISC_R_INVALIDFILE);
return(ISC_R_SUCCESS);
}
isc_result_t
isc_file_isplainfilefd(int fd) {
/*
* This function returns success if filename is a plain file.
*/
struct stat filestat;
memset(&filestat,0,sizeof(struct stat));
if ((fstat(fd, &filestat)) == -1)
return(isc__errno2result(errno));
if(! S_ISREG(filestat.st_mode))
return(ISC_R_INVALIDFILE);
return(ISC_R_SUCCESS);
}
isc_result_t
isc_file_isdirectory(const char *filename) {
/*
* This function returns success if filename is a directory.
*/
struct stat filestat;
memset(&filestat,0,sizeof(struct stat));
if ((stat(filename, &filestat)) == -1)
return(isc__errno2result(errno));
if(! S_ISDIR(filestat.st_mode))
return(ISC_R_INVALIDFILE);
return(ISC_R_SUCCESS);
}
isc_boolean_t
isc_file_isabsolute(const char *filename) {
REQUIRE(filename != NULL);
/*
* Look for c:\path\... style, c:/path/... or \\computer\shar\path...
* the UNC style file specs
*/
if ((filename[0] == '\\') && (filename[1] == '\\'))
return (ISC_TRUE);
if (isalpha(filename[0]) && filename[1] == ':' && filename[2] == '\\')
return (ISC_TRUE);
if (isalpha(filename[0]) && filename[1] == ':' && filename[2] == '/')
return (ISC_TRUE);
return (ISC_FALSE);
}
isc_boolean_t
isc_file_iscurrentdir(const char *filename) {
REQUIRE(filename != NULL);
return (ISC_TF(filename[0] == '.' && filename[1] == '\0'));
}
isc_boolean_t
isc_file_ischdiridempotent(const char *filename) {
REQUIRE(filename != NULL);
if (isc_file_isabsolute(filename))
return (ISC_TRUE);
if (filename[0] == '\\')
return (ISC_TRUE);
if (filename[0] == '/')
return (ISC_TRUE);
if (isc_file_iscurrentdir(filename))
return (ISC_TRUE);
return (ISC_FALSE);
}
const char *
isc_file_basename(const char *filename) {
char *s;
REQUIRE(filename != NULL);
s = strrchr(filename, '\\');
if (s == NULL)
return (filename);
return (s + 1);
}
isc_result_t
isc_file_progname(const char *filename, char *progname, size_t namelen) {
const char *s;
const char *p;
size_t len;
REQUIRE(filename != NULL);
REQUIRE(progname != NULL);
/*
* Strip the path from the name
*/
s = isc_file_basename(filename);
if (s == NULL) {
return (ISC_R_NOSPACE);
}
/*
* Strip any and all suffixes
*/
p = strchr(s, '.');
if (p == NULL) {
if (namelen <= strlen(s))
return (ISC_R_NOSPACE);
strlcpy(progname, s, namelen);
return (ISC_R_SUCCESS);
}
/*
* Copy the result to the buffer
*/
len = p - s;
if (len >= namelen)
return (ISC_R_NOSPACE);
/* Copy up to 'len' bytes and NUL terminate. */
strlcpy(progname, s, ISC_MIN(len + 1, namelen));
return (ISC_R_SUCCESS);
}
isc_result_t
isc_file_absolutepath(const char *filename, char *path, size_t pathlen) {
char *ptrname;
DWORD retval;
REQUIRE(filename != NULL);
REQUIRE(path != NULL);
retval = GetFullPathName(filename, (DWORD) pathlen, path, &ptrname);
/* Something went wrong in getting the path */
if (retval == 0)
return (ISC_R_NOTFOUND);
/* Caller needs to provide a larger buffer to contain the string */
if (retval >= pathlen)
return (ISC_R_NOSPACE);
return (ISC_R_SUCCESS);
}
isc_result_t
isc_file_truncate(const char *filename, isc_offset_t size) {
int fh;
REQUIRE(filename != NULL && size >= 0);
if ((fh = open(filename, _O_RDWR | _O_BINARY)) < 0)
return (isc__errno2result(errno));
if(_chsize(fh, size) != 0) {
close(fh);
return (isc__errno2result(errno));
}
close(fh);
return (ISC_R_SUCCESS);
}
isc_result_t
isc_file_safecreate(const char *filename, FILE **fp) {
isc_result_t result;
int flags;
struct stat sb;
FILE *f;
int fd;
REQUIRE(filename != NULL);
REQUIRE(fp != NULL && *fp == NULL);
result = file_stats(filename, &sb);
if (result == ISC_R_SUCCESS) {
if ((sb.st_mode & S_IFREG) == 0)
return (ISC_R_INVALIDFILE);
flags = O_WRONLY | O_TRUNC;
} else if (result == ISC_R_FILENOTFOUND) {
flags = O_WRONLY | O_CREAT | O_EXCL;
} else
return (result);
fd = open(filename, flags, S_IRUSR | S_IWUSR);
if (fd == -1)
return (isc__errno2result(errno));
f = fdopen(fd, "w");
if (f == NULL) {
result = isc__errno2result(errno);
close(fd);
return (result);
}
*fp = f;
return (ISC_R_SUCCESS);
}
isc_result_t
isc_file_splitpath(isc_mem_t *mctx, const char *path, char **dirname,
char const ** basename)
{
char *dir;
const char *file, *slash;
char *backslash;
slash = strrchr(path, '/');
backslash = strrchr(path, '\\');
if ((slash != NULL && backslash != NULL && backslash > slash) ||
(slash == NULL && backslash != NULL))
slash = backslash;
if (slash == path) {
file = ++slash;
dir = isc_mem_strdup(mctx, "/");
} else if (slash != NULL) {
file = ++slash;
dir = isc_mem_allocate(mctx, slash - path);
if (dir != NULL)
strlcpy(dir, path, slash - path);
} else {
file = path;
dir = isc_mem_strdup(mctx, ".");
}
if (dir == NULL)
return (ISC_R_NOMEMORY);
if (*file == '\0') {
isc_mem_free(mctx, dir);
return (ISC_R_INVALIDFILE);
}
*dirname = dir;
*basename = file;
return (ISC_R_SUCCESS);
}
void *
isc_file_mmap(void *addr, size_t len, int prot,
int flags, int fd, off_t offset)
{
void *buf;
ssize_t ret;
off_t end;
UNUSED(addr);
UNUSED(prot);
UNUSED(flags);
end = lseek(fd, 0, SEEK_END);
lseek(fd, offset, SEEK_SET);
if (end - offset < (off_t) len)
len = end - offset;
buf = malloc(len);
if (buf == NULL)
return (NULL);
ret = read(fd, buf, (unsigned int) len);
if (ret != (ssize_t) len) {
free(buf);
buf = NULL;
}
return (buf);
}
int
isc_file_munmap(void *addr, size_t len) {
UNUSED(len);
free(addr);
return (0);
}
#define DISALLOW "\\/:ABCDEFGHIJKLMNOPQRSTUVWXYZ"
#ifndef PATH_MAX
#define PATH_MAX 1024
#endif
isc_result_t
isc_file_sanitize(const char *dir, const char *base, const char *ext,
char *path, size_t length)
{
char buf[PATH_MAX], hash[ISC_SHA256_DIGESTSTRINGLENGTH];
size_t l = 0;
REQUIRE(base != NULL);
REQUIRE(path != NULL);
l = strlen(base) + 1;
/*
* allow room for a full sha256 hash (64 chars
* plus null terminator)
*/
if (l < 65)
l = 65;
if (dir != NULL)
l += strlen(dir) + 1;
if (ext != NULL)
l += strlen(ext) + 1;
if (l > length || l > PATH_MAX)
return (ISC_R_NOSPACE);
/* Check whether the full-length SHA256 hash filename exists */
isc_sha256_data((const void *) base, strlen(base), hash);
snprintf(buf, sizeof(buf), "%s%s%s%s%s",
dir != NULL ? dir : "", dir != NULL ? "/" : "",
hash, ext != NULL ? "." : "", ext != NULL ? ext : "");
if (isc_file_exists(buf)) {
strlcpy(path, buf, length);
return (ISC_R_SUCCESS);
}
/* Check for a truncated SHA256 hash filename */
hash[16] = '\0';
snprintf(buf, sizeof(buf), "%s%s%s%s%s",
dir != NULL ? dir : "", dir != NULL ? "/" : "",
hash, ext != NULL ? "." : "", ext != NULL ? ext : "");
if (isc_file_exists(buf)) {
strlcpy(path, buf, length);
return (ISC_R_SUCCESS);
}
/*
* If neither hash filename already exists, then we'll use
* the original base name if it has no disallowed characters,
* or the truncated hash name if it does.
*/
if (strpbrk(base, DISALLOW) != NULL) {
strlcpy(path, buf, length);
return (ISC_R_SUCCESS);
}
snprintf(buf, sizeof(buf), "%s%s%s%s%s",
dir != NULL ? dir : "", dir != NULL ? "/" : "",
base, ext != NULL ? "." : "", ext != NULL ? ext : "");
strlcpy(path, buf, length);
return (ISC_R_SUCCESS);
}
/*
* Based on http://blog.aaronballman.com/2011/08/how-to-check-access-rights/
*/
isc_boolean_t
isc_file_isdirwritable(const char *path) {
DWORD length = 0;
HANDLE hToken = NULL;
PSECURITY_DESCRIPTOR security = NULL;
isc_boolean_t answer = ISC_FALSE;
if (isc_file_isdirectory(path) != ISC_R_SUCCESS) {
return (answer);
}
/*
* Figure out buffer size. GetFileSecurity() should not succeed.
*/
if (GetFileSecurity(path, OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION,
NULL, 0, &length))
{
return (answer);
}
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
return (answer);
}
security = malloc(length);
if (security == NULL) {
return (answer);
}
/*
* GetFileSecurity() should succeed.
*/
if (!GetFileSecurity(path, OWNER_SECURITY_INFORMATION |
GROUP_SECURITY_INFORMATION |
DACL_SECURITY_INFORMATION,
security, length, &length))
{
return (answer);
}
if (OpenProcessToken(GetCurrentProcess(), TOKEN_IMPERSONATE |
TOKEN_QUERY | TOKEN_DUPLICATE |
STANDARD_RIGHTS_READ, &hToken))
{
HANDLE hImpersonatedToken = NULL;
if (DuplicateToken(hToken, SecurityImpersonation,
&hImpersonatedToken))
{
GENERIC_MAPPING mapping;
PRIVILEGE_SET privileges = { 0 };
DWORD grantedAccess = 0;
DWORD privilegesLength = sizeof(privileges);
BOOL result = FALSE;
DWORD genericAccessRights = GENERIC_WRITE;
mapping.GenericRead = FILE_GENERIC_READ;
mapping.GenericWrite = FILE_GENERIC_WRITE;
mapping.GenericExecute = FILE_GENERIC_EXECUTE;
mapping.GenericAll = FILE_ALL_ACCESS;
MapGenericMask(&genericAccessRights, &mapping);
if (AccessCheck(security, hImpersonatedToken,
genericAccessRights, &mapping,
&privileges, &privilegesLength,
&grantedAccess, &result))
{
answer = ISC_TF(result);
}
CloseHandle(hImpersonatedToken);
}
CloseHandle(hToken);
}
free(security);
return (answer);
}