summaryrefslogtreecommitdiff
path: root/util/nvmutil/lib/file.c
diff options
context:
space:
mode:
authorLeah Rowe <leah@libreboot.org>2026-03-20 04:02:51 +0000
committerLeah Rowe <leah@libreboot.org>2026-03-22 13:50:44 +0000
commit6838db4647b600bf5b356429f54850bf801e7ba4 (patch)
treecc98541897703d2949af27dc050cad8cba5061a0 /util/nvmutil/lib/file.c
parentf50ffd6bb13c04cb185fb6311f8875582bf18388 (diff)
WIP: hardened mktemp
i'm pretty much nearly there. still no dir support, only files. i won't keep amending now - will do more, then squash later. Signed-off-by: Leah Rowe <leah@libreboot.org>
Diffstat (limited to 'util/nvmutil/lib/file.c')
-rw-r--r--util/nvmutil/lib/file.c1727
1 files changed, 1480 insertions, 247 deletions
diff --git a/util/nvmutil/lib/file.c b/util/nvmutil/lib/file.c
index b4925ccd..e722cb6a 100644
--- a/util/nvmutil/lib/file.c
+++ b/util/nvmutil/lib/file.c
@@ -24,16 +24,29 @@ same_file(int fd, struct stat *st_old,
struct stat st;
int saved_errno = errno;
- if (st_old == NULL || fd < 0)
+ /* TODO: null/-1 checks
+ * like this can be
+ * generalised
+ */
+ if (st_old == NULL) {
+ errno = EFAULT;
+ goto err_same_file;
+ }
+ if (fd < 0) {
+ errno = EBADF;
goto err_same_file;
+ }
if (fstat(fd, &st) == -1)
- return -1;
+ goto err_same_file;
if (st.st_dev != st_old->st_dev ||
st.st_ino != st_old->st_ino ||
- !S_ISREG(st.st_mode))
+ !S_ISREG(st.st_mode)) {
+
+ errno = ESTALE;
goto err_same_file;
+ }
if (check_size &&
st.st_size != st_old->st_size)
@@ -44,13 +57,24 @@ same_file(int fd, struct stat *st_old,
err_same_file:
- errno = EIO;
+ if (errno == saved_errno)
+ errno = ESTALE;
+
return -1;
}
/* open() but with abort traps
*/
+/* TODO: also support other things here than files.
+ and then use, throughout the program.
+ in particular, use of openat might help
+ (split the path)
+ (see: link attack mitigations throughout nvmutil)
+ make it return, and handle the return value/errno
+
+ (this could return e.g. EINTR)
+ */
void
xopen(int *fd_ptr, const char *path, int flags, struct stat *st)
{
@@ -76,30 +100,32 @@ fsync_dir(const char *path)
{
int saved_errno = errno;
- unsigned long pathlen;
- unsigned long maxlen;
+ size_t pathlen = 0;
+ size_t maxlen = 0;
- char *dirbuf;
- int dirfd;
+ char *dirbuf = NULL;
+ int dirfd = -1;
- char *slash;
-
- struct stat st;
+ char *slash = NULL;
+ struct stat st = {0};
#if defined(PATH_LEN) && \
(PATH_LEN) >= 256
maxlen = PATH_LEN;
#else
- maxlen = 1024;
+ maxlen = 4096;
#endif
- dirbuf = NULL;
- dirfd = -1;
+ if (path == NULL) {
+ errno = EFAULT;
+ goto err_fsync_dir;
+ }
- pathlen = xstrxlen(path, maxlen);
+ if (slen(path, maxlen, &pathlen) < 0)
+ goto err_fsync_dir;
- if (pathlen >= maxlen) {
- fprintf(stderr, "Path too long for fsync_parent_dir\n");
+ if (pathlen >= maxlen || pathlen < 0) {
+ errno = EMSGSIZE;
goto err_fsync_dir;
}
@@ -110,8 +136,11 @@ fsync_dir(const char *path)
}
dirbuf = malloc(pathlen + 1);
- if (dirbuf == NULL)
+ if (dirbuf == NULL) {
+
+ errno = ENOMEM;
goto err_fsync_dir;
+ }
memcpy(dirbuf, path, pathlen + 1);
slash = strrchr(dirbuf, '/');
@@ -127,7 +156,7 @@ fsync_dir(const char *path)
dirbuf[1] = '\0';
}
- dirfd = open(dirbuf, O_RDONLY
+ dirfd = open(dirbuf, O_RDONLY | O_CLOEXEC | O_NOCTTY
#ifdef O_DIRECTORY
| O_DIRECTORY
#endif
@@ -135,14 +164,26 @@ fsync_dir(const char *path)
| O_NOFOLLOW
#endif
);
- if (dirfd == -1)
+ if (dirfd < 0)
+ goto err_fsync_dir;
+
+ /* symlink/directory replacement
+ attack mitigation
+ */
+ if (check_dirfd(dirfd, dirbuf) < 0) {
+
+ (void) close_on_eintr(dirfd);
+
+ dirfd = -1;
goto err_fsync_dir;
+ }
if (fstat(dirfd, &st) < 0)
goto err_fsync_dir;
if (!S_ISDIR(st.st_mode)) {
- fprintf(stderr, "%s: not a directory\n", dirbuf);
+
+ errno = ENOTDIR;
goto err_fsync_dir;
}
@@ -150,316 +191,1420 @@ fsync_dir(const char *path)
if (fsync_on_eintr(dirfd) == -1)
goto err_fsync_dir;
- if (close_on_eintr(dirfd) == -1)
+ if (close_on_eintr(dirfd) == -1) {
+
+ dirfd = -1;
goto err_fsync_dir;
+ }
+
+ if (dirbuf != NULL) {
- if (dirbuf != NULL)
free(dirbuf);
+ dirbuf = NULL;
+ }
+
+ dirbuf = NULL;
errno = saved_errno;
return 0;
err_fsync_dir:
- if (!errno)
+
+ if (errno == saved_errno)
errno = EIO;
- if (errno != saved_errno)
- fprintf(stderr, "%s: %s\n", path, strerror(errno));
+ if (dirbuf != NULL) {
- if (dirbuf != NULL)
free(dirbuf);
+ dirbuf = NULL;
+ }
- if (dirfd > -1)
- close_on_eintr(dirfd);
+ if (dirfd >= 0) {
- errno = saved_errno;
+ (void) close_on_eintr(dirfd);
+ dirfd = -1;
+ }
return -1;
}
-/* returns ptr to path (string). if local>0:
- * make tmpfile in the same directory as the
- * file. if local==0, use TMPDIR
+/* hardened tmpfile creation
+ *
+ * if not local, a standard world
+ * writeable directory (e.g. /tmp)
+ * will be used. otherwise,
+ * the path is simply suffixed for
+ * local tmp file (no world check)
+ *
+ * sets a file descriptor by pointer
+ * fd, and returns the path as a
+ * string (for your new tmp file)
*
- * if local==0, the 3rd argument is ignored
+ * on error, the descriptor will be
+ * set to -1 and errno will be set,
+ * to indicate the error, and then
+ * a NULL pointer will be returned.
*/
char *
-new_tmpfile(int *fd, int local, const char *path)
+new_tmpfile(int *fd, int local,
+ const char *path)
{
- unsigned long maxlen;
+/* TODO:
+ * directory support (currently only files)
+ */
+ size_t maxlen;
struct stat st;
- /* please do not modify the
- * strings or I will get mad
+ char suffix[] =
+ "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
+ char *tmpdir = NULL;
+
+ size_t dirlen;
+ size_t pathlen;
+ size_t destlen;
+ size_t baselen;
+
+ char *dest = NULL; /* final path */
+
+ int saved_errno = errno;
+
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation
+ in mkhtemp()
+ */
+ int dirfd = -1;
+ const char *fname = NULL;
+
+ /* open the directory early,
+ * check via fd throughout,
+ * for directory replacement
+ * attack mitigation
*/
- char tmp_none[] = "";
- char tmp_default[] = "/tmp";
- char default_tmpname[] = "tmpXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
- char *tmpname;
+ struct stat st_dir_initial;
+#endif
- char *base = NULL;
- char *dest = NULL;
+ struct path_split ps;
- unsigned long tmpdir_len = 0;
- unsigned long tmpname_len = 0;
- unsigned long tmppath_len = 0;
+ if (fd == NULL) {
- int fd_tmp = -1;
- int flags;
+ errno = EFAULT;
+ goto err_new_tmpfile;
+ }
+
+ /* block operating on
+ * someone elses file
+ */
+ if (*fd >= 0) {
+
+ errno = EEXIST;
+ goto err_new_tmpfile;
+
+ /* they might
+ * want it later!
+ */
+ }
+ /* but now it's *mine*:
+ */
+ *fd = -1;
#if defined(PATH_LEN) && \
(PATH_LEN) >= 256
+
maxlen = PATH_LEN;
#else
- maxlen = 1024;
+ maxlen = 4096;
#endif
- tmpname = default_tmpname;
+ /* base dir e.g. /tmp
+ */
+ if (!local) {
+
+#if defined(PERMIT_NON_STICKY_ALWAYS) && \
+ ((PERMIT_NON_STICKY_ALWAYS) > 0)
+
+ tmpdir = env_tmpdir(PERMIT_NON_STICKY_ALWAYS);
+#else
+ tmpdir = env_tmpdir(0);
+#endif
+ if (tmpdir == NULL)
+ goto err_new_tmpfile;
+ }
+
+ /* local means we want
+ * e.g. hello.txt to then
+ * have a tmpfile created
+ * for it. useful for
+ * atomic writes
+ */
if (local) {
- if (path == NULL)
+
+ if (path == NULL) {
+ errno = EFAULT;
goto err_new_tmpfile;
- if (*path == '\0')
+ }
+ if (*path == '\0') {
+ errno = EINVAL;
goto err_new_tmpfile;
+ }
- if (stat(path, &st) == -1)
+ if (slen(path, maxlen, &pathlen) < 0)
goto err_new_tmpfile;
- if (!S_ISREG(st.st_mode))
+ if (path[pathlen - 1] == '/') {
+ errno = EINVAL;
goto err_new_tmpfile;
+ }
- tmpname = (char *)path;
- }
-
- if (local) {
- base = tmp_none;
+ dirlen = 0;
- /* appended to filename for tmp:
- */
- tmpdir_len = xstrxlen(default_tmpname, maxlen);
} else {
- base = get_tmpdir();
- if (base == NULL)
- base = tmp_default;
- if (*base == '\0')
- base = tmp_default;
+ if (slen(tmpdir, maxlen, &dirlen) < 0)
+ goto err_new_tmpfile;
- tmpdir_len = xstrxlen(base, maxlen);
+ pathlen = 0;
}
- tmpname_len = xstrxlen(tmpname, maxlen);
-
- tmppath_len = tmpdir_len + tmpname_len;
- ++tmppath_len; /* for '/' or '.' */
+ /* now we want the base dir,
+ * with the file appended,
+ * and the XXXXXXXXXX suffix
+ */
- /* max length -1 of maxlen
- * for termination
+ /* using sizeof (not slen) adds an extra byte,
+ * useful because we either want '.' or '/'
*/
- if (tmpdir_len > maxlen - tmpname_len - 1)
+ destlen = dirlen + pathlen + sizeof(suffix);
+
+ if (destlen > maxlen - 1) { /* -1 for NULL */
+
+ errno = EOVERFLOW;
goto err_new_tmpfile;
+ }
+
+ dest = malloc(destlen + 1); /* +1 for NULL */
+ if (dest == NULL) {
- /* +1 for NULL */
- dest = malloc(tmppath_len + 1);
- if (dest == NULL)
+ errno = ENOMEM;
goto err_new_tmpfile;
+ }
+
+ /* As you see above, we only allow
+ * either a base tmpdir and suffix,
+ * or a user-supplied file and we
+ * suffix that.
+ */
+ if (dirlen > 0 && pathlen > 0) {
+
+ errno = EINVAL;
+ goto err_new_tmpfile; /* pre-emptive fix */
+ }
if (local) {
- *dest = '.'; /* hidden file */
+ if (split_path(path, &ps) < 0)
+ goto err_new_tmpfile;
+
+ if (slen(ps.base, maxlen, &baselen) < 0)
+ goto err_new_tmpfile;
+
+ /* ALWAYS set this right after
+ * split path, to avoid leaking fd:
+ */
+ dirfd = ps.dirfd;
+
+ *(dest) = '.';
- memcpy(dest + (unsigned long)1, tmpname, tmpname_len);
+ memcpy(dest + 1, ps.base, baselen);
+
+ memcpy(dest + 1 + baselen,
+ suffix, sizeof(suffix) - 1);
+
+#if !(defined(DISABLE_OPENAT) && \
+((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation
+ * in mkhtemp(
+ */
+ fname = dest + 1;
+#endif
+
+ if (ps.buf != NULL) {
+ free(ps.buf);
+ ps.buf = NULL;
+ }
- memcpy(dest + (unsigned long)1 + tmpname_len,
- default_tmpname, tmpdir_len);
} else {
- memcpy(dest, base, tmpdir_len);
+ memcpy(dest, tmpdir, dirlen);
- dest[tmpdir_len] = '/';
+ *(dest + dirlen) = '/';
- memcpy(dest + tmpdir_len + 1, tmpname, tmpname_len);
+ memcpy(dest + dirlen + 1, suffix,
+ sizeof(suffix) - 1);
+
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation
+ in mkhtemp()
+ */
+ dirfd = open_verified_dir(tmpdir);
+ if (dirfd < 0)
+ goto err_new_tmpfile;
+
+ /* we will use this later, throughout,
+ * for detecting **directory replacement**
+ */
+ if (fstat(dirfd, &st_dir_initial) < 0)
+ goto err_new_tmpfile;
+
+ fname = dest + dirlen + 1;
+#endif
}
+ *(dest + destlen) = '\0';
- dest[tmppath_len] = '\0';
- fd_tmp = mkstemp_n(dest);
- if (fd_tmp == -1)
- goto err_new_tmpfile;
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation
+ in mkhtemp()
+ */
+ *fd = mkhtemp(fd, &st, dest, dirfd, fname, &st_dir_initial);
+#else
+ *fd = mkhtemp(fd, &st, dest);
+#endif
- if (fchmod(fd_tmp, 0600) == -1)
+ if (*fd < 0)
goto err_new_tmpfile;
- flags = fcntl(fd_tmp, F_GETFL);
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation
+ in mkhtemp()
+ */
+ if (dirfd >= 0) {
+ (void) close_on_eintr(dirfd);
+ dirfd = -1;
+ }
+#endif
- if (flags == -1)
- goto err_new_tmpfile;
+ errno = saved_errno;
+ return dest;
+
+err_new_tmpfile:
- /*
- * O_APPEND would permit offsets
- * to be ignored, which breaks
- * positional read/write
- */
- if (flags & O_APPEND)
- goto err_new_tmpfile;
+ if (errno != saved_errno)
+ saved_errno = errno;
+ else
+ saved_errno = errno = EIO;
- if (lock_file(fd_tmp, flags) == -1)
- goto err_new_tmpfile;
+ if (dest != NULL) {
+ free(dest);
+ dest = NULL;
+ }
- if (fstat(fd_tmp, &st) == -1)
- goto err_new_tmpfile;
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0)) /* for openat dir replacement mitigation
+ in mkhtemp()
+ */
+ if (dirfd >= 0) {
- /*
- * Extremely defensive
- * likely pointless checks
- */
+ (void) close_on_eintr(dirfd);
+ dirfd = -1;
+ }
+#endif
- /* check if it's a file */
- if (!S_ISREG(st.st_mode))
- goto err_new_tmpfile;
+ if (*fd >= 0) {
- /* check if it's seekable */
- if (lseek(fd_tmp, 0, SEEK_CUR) == (off_t)-1)
- goto err_new_tmpfile;
+ (void) close_on_eintr(*fd);
+ *fd = -1;
+ }
- /* tmpfile has >1 hardlinks */
- if (st.st_nlink > 1)
- goto err_new_tmpfile;
+ errno = saved_errno;
- /* tmpfile unlinked while opened */
- if (st.st_nlink == 0)
- goto err_new_tmpfile;
+ return NULL;
+}
- *fd = fd_tmp;
+/* hardened TMPDIR parsing
+ */
- return dest;
+char *
+env_tmpdir(int bypass_all_sticky_checks)
+{
+ char *t;
+ int allow_noworld_unsticky;
+ int saved_errno = errno;
-err_new_tmpfile:
+ t = getenv("TMPDIR");
- if (dest != NULL)
- free(dest);
+ if (t != NULL && *t != '\0') {
+
+ if (tmpdir_policy(t,
+ &allow_noworld_unsticky) == 0) {
- if (fd_tmp > -1)
- close_on_eintr(fd_tmp);
+ if (world_writeable_and_sticky(t,
+ allow_noworld_unsticky,
+ bypass_all_sticky_checks)) {
+
+ errno = saved_errno;
+ return t;
+ }
+ }
+ }
+
+ allow_noworld_unsticky = 0;
+
+ if (world_writeable_and_sticky("/tmp",
+ allow_noworld_unsticky,
+ bypass_all_sticky_checks)) {
+
+ errno = saved_errno;
+ return "/tmp";
+ }
+
+ if (world_writeable_and_sticky("/var/tmp",
+ allow_noworld_unsticky,
+ bypass_all_sticky_checks)) {
+
+ errno = saved_errno;
+ return "/var/tmp";
+ }
+
+ if (errno == saved_errno)
+ errno = EPERM;
return NULL;
}
int
-lock_file(int fd, int flags)
+tmpdir_policy(const char *path,
+ int *allow_noworld_unsticky)
{
- struct flock fl;
+ int saved_errno = errno;
+ int r;
- memset(&fl, 0, sizeof(fl));
+ if (path == NULL ||
+ allow_noworld_unsticky == NULL) {
- if ((flags & O_ACCMODE) == O_RDONLY)
- fl.l_type = F_RDLCK;
- else
- fl.l_type = F_WRLCK;
+ errno = EFAULT;
+ return -1;
+ }
- fl.l_whence = SEEK_SET;
+ *allow_noworld_unsticky = 1;
- if (fcntl(fd, F_SETLK, &fl) == -1)
- return -1;
+ r = same_dir(path, "/tmp");
+ if (r < 0)
+ goto err_tmpdir_policy;
+ if (r > 0)
+ *allow_noworld_unsticky = 0;
+ r = same_dir(path, "/var/tmp");
+ if (r < 0)
+ goto err_tmpdir_policy;
+ if (r > 0)
+ *allow_noworld_unsticky = 0;
+
+ errno = saved_errno;
return 0;
+
+err_tmpdir_policy:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
}
-/* return TMPDIR, or fall back
- * to portable defaults
- */
+int
+same_dir(const char *a, const char *b)
+{
+ int fd_a = -1;
+ int fd_b = -1;
-char *
-get_tmpdir(void)
+ struct stat st_a;
+ struct stat st_b;
+
+ int saved_errno = errno;
+ int rval_scmp;
+
+#if defined(PATH_LEN) && \
+ (PATH_LEN) >= 256
+ size_t maxlen = (PATH_LEN);
+#else
+ size_t maxlen = 4096;
+#endif
+
+ /* optimisation: if both dirs
+ are the same, we don't need
+ to check anything. sehr schnell:
+ */
+ if (scmp(a, b, maxlen, &rval_scmp) < 0)
+ goto err_same_dir;
+ /* bonus: scmp checks null for us */
+ if (rval_scmp == 0)
+ goto success_same_dir;
+
+ fd_a = open_verified_dir(a);
+ if (fd_a < 0)
+ goto err_same_dir;
+
+ fd_b = open_verified_dir(b);
+ if (fd_b < 0)
+ goto err_same_dir;
+
+ if (fstat(fd_a, &st_a) < 0)
+ goto err_same_dir;
+
+ if (fstat(fd_b, &st_b) < 0)
+ goto err_same_dir;
+
+ if (st_a.st_dev == st_b.st_dev &&
+ st_a.st_ino == st_b.st_ino) {
+
+ (void) close_on_eintr(fd_a);
+ (void) close_on_eintr(fd_b);
+
+success_same_dir:
+
+ /* SUCCESS
+ */
+
+ errno = saved_errno;
+ return 1;
+ }
+
+ (void) close_on_eintr(fd_a);
+ (void) close_on_eintr(fd_b);
+
+ /* FAILURE (logical)
+ */
+
+ errno = saved_errno;
+ return 0;
+
+err_same_dir:
+
+ /* FAILURE (probably syscall)
+ */
+
+ if (fd_a >= 0)
+ (void) close_on_eintr(fd_a);
+ if (fd_b >= 0)
+ (void) close_on_eintr(fd_b);
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}
+
+/* bypass_all_sticky_checks: if set,
+ disable stickiness checks (libc behaviour)
+ (if not set: leah behaviour)
+
+ allow_noworld_unsticky:
+ allow non-sticky files if not world-writeable
+ (still block non-sticky in standard TMPDIR)
+*/
+int
+world_writeable_and_sticky(
+ const char *s,
+ int allow_noworld_unsticky,
+ int bypass_all_sticky_checks)
{
- char *t;
struct stat st;
+ int dirfd = -1;
- t = getenv("TMPDIR");
+ int saved_errno = errno;
- if (t && *t) {
+ if (s == NULL || *s == '\0') {
+ errno = EINVAL;
+ goto sticky_hell;
+ }
- if (stat(t, &st) == 0 && S_ISDIR(st.st_mode)) {
+ /* mitigate symlink attacks*
+ */
+ dirfd = open_verified_dir(s);
+ if (dirfd < 0)
+ goto sticky_hell;
- if ((st.st_mode & S_IWOTH) && !(st.st_mode & S_ISVTX))
- return NULL;
+ if (fstat(dirfd, &st) < 0)
+ goto sticky_hell;
- return t;
- }
+ if (!S_ISDIR(st.st_mode)) {
+ errno = ENOTDIR;
+ goto sticky_hell;
}
- if (stat("/tmp", &st) == 0 && S_ISDIR(st.st_mode))
- return "/tmp";
+ /* must be fully executable
+ * by everyone, or openat(2)
+ * becomes unreliable**
+ */
+ if (!(st.st_mode & S_IXUSR) ||
+ !(st.st_mode & S_IXGRP) ||
+ !(st.st_mode & S_IXOTH)) {
- if (stat("/var/tmp", &st) == 0 && S_ISDIR(st.st_mode))
- return "/var/tmp";
+ errno = EACCES;
+ goto sticky_hell;
+ }
+
+ /* *normal-**ish mode (libc):
+ */
+
+ if (bypass_all_sticky_checks)
+ goto sticky_heaven; /* normal == no security */
+
+ /* unhinged leah mode:
+ */
+
+ if (is_owner(&st) < 0)
+ goto sticky_hell;
+
+ if (st.st_mode & S_IWOTH) { /* world writeable */
+
+ /* if world-writeable, only
+ * allow sticky files
+ */
+ if (st.st_mode & S_ISVTX)
+ goto sticky_heaven; /* sticky */
+
+ errno = EPERM;
+ goto sticky_hell; /* not sticky */
+ }
+
+ /* non-world-writeable, so
+ * stickiness is do-not-care
+ */
+ if (allow_noworld_unsticky)
+ goto sticky_heaven; /* sticky! */
+
+ goto sticky_hell; /* definitely not sticky */
+
+sticky_heaven:
+/* i like the one in hamburg better */
+
+ if (dirfd >= 0)
+ (void) close_on_eintr(dirfd);
+
+ errno = saved_errno;
- return ".";
+ return 1;
+
+sticky_hell:
+
+ if (errno == saved_errno)
+ errno = EPERM;
+
+ saved_errno = errno;
+
+ if (dirfd >= 0)
+ (void) close_on_eintr(dirfd);
+
+ errno = saved_errno;
+
+ return 0;
}
-/* portable mkstemp
+/* mk(h)temp - hardened mktemp.
+ * like mkstemp, but (MUCH) harder.
+ * TODO:
+ * directory support (currently only
+ * generates files)
*/
+#if defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0)
int
-mkstemp_n(char *template)
+mkhtemp(int *fd,
+ struct stat *st,
+ char *template)
+#else
+int mkhtemp(int *fd,
+ struct stat *st,
+ char *template,
+ int dirfd,
+ const char *fname,
+ struct stat *st_dir_initial)
+#endif
{
- int fd;
- unsigned long i, j;
- unsigned long len;
- char *p;
+ /* NOTE: this function currently
+ * only supports *files*.
+ * it doesn't make tmp*dirs*
+ */
+
+ size_t retries = 0;
+#if !(defined(MAX_MKHTEMP_RETRIES) && \
+ (MAX_MKHTEMP_RETRIES) >= 128)
+ size_t max_retries = 200;
+#else
+ size_t max_retries = 128;
+#endif
+
+ size_t chx = 0;
+ size_t len = 0;
+ char *p = NULL;
+ char *template_copy = NULL;
- unsigned long xc = 0;
+ size_t xc = 0;
static char ch[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ size_t limit =
+ ((size_t)-1) - (((size_t)-1) % (sizeof(ch) - 1));
- unsigned long r;
+ int rand_failures = 0;
+
+ size_t r;
#if defined(PATH_LEN) && \
- (PATH_LEN) >= 256
- unsigned long max_len = PATH_LEN;
+ (PATH_LEN) >= 256
+ size_t max_len = PATH_LEN;
#else
- unsigned long max_len = 4096;
+ size_t max_len = 4096;
#endif
- len = xstrxlen(template, max_len);
+ int file_created = 0;
+
+ int saved_errno = errno;
+ struct stat st_tmp;
+
+ mode_t old_umask;
- if (len < 6) {
+ int saved_rand_error = 0;
+
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0))
+ char *fname_copy = NULL;
+ size_t fname_len = 0;
+
+ /* for ctrl char check
+ */
+ unsigned char ctrl = 0;
+ size_t ctrl_pos = 0;
+
+ /* in openat mode, we re-check
+ directory against previous
+ check done by the caller,
+ * mitigating symlink attacks
+ */
+ struct stat st_dir_now;
+
+ if (fname == NULL ||
+ st_dir_initial == NULL) {
+
+ errno = EFAULT;
+ goto err_mkhtemp;
+ }
+
+ if (slen(fname, max_len, &fname_len) < 0)
+ goto err_mkhtemp;
+
+ if (fname_len == 0) {
errno = EINVAL;
- return -1;
+ goto err_mkhtemp;
}
+ if (dirfd < 0) {
+
+ errno = EBADF;
+ goto err_mkhtemp;
+ }
+#endif
+
+ if (fd == NULL ||
+ template == NULL) {
+
+ errno = EFAULT;
+ goto err_mkhtemp;
+ }
+
+ if (*fd >= 0) {
+
+ errno = EEXIST;
+ goto err_mkhtemp;
+ }
+
+ if (slen(template, max_len, &len) < 0)
+ goto err_mkhtemp;
+
+ /* bounds check */
+ if (len >= max_len) {
+ errno = EMSGSIZE;
+ goto err_mkhtemp;
+ }
+
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0))
+
+ if (strrchr(fname, '/') != NULL) {
+
+ /* otherwise, a mangled
+ path could leave the
+ directory, defeating
+ the purpose of openat
+ */
+ errno = EINVAL;
+ goto err_mkhtemp;
+ }
+
+ /* reject dangerous basenames
+ */
+ if (fname[0] == '\0' ||
+ (fname[0] == '.' && fname[1] == '\0') ||
+ (fname[0] == '.' && fname[1] == '.' && fname[2] == '\0')) {
+
+ errno = EINVAL;
+ goto err_mkhtemp;
+ }
+ /* block control chars
+ */
+ for (ctrl_pos = 0;
+ ctrl_pos < fname_len;
+ ctrl_pos++) {
+
+ ctrl = (unsigned char)fname[ctrl_pos];
+
+ if (ctrl < 32 || ctrl == 127) {
+
+ errno = EINVAL;
+ goto err_mkhtemp;
+ }
+ }
+
+#endif
+
p = template + len;
- while (p > template && p[-1] == 'X') {
- --p;
+ while (p > template &&
+ *--p == 'X')
++xc;
+
+ if (xc < 6 || xc > len) {
+
+ errno = EINVAL;
+ goto err_mkhtemp;
}
- if (xc < 6) {
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0))
+
+ if (fname_len > len ||
+ fname_len > (len - xc)) {
+
+ /* prevent overflow
+ */
+ errno = EOVERFLOW;
+ goto err_mkhtemp;
+ }
+
+ if (memcmp(fname,
+ template + len - fname_len,
+ fname_len) != 0) {
+
errno = EINVAL;
+ goto err_mkhtemp;
+ }
+
+#endif
+
+ template_copy = malloc(len + 1);
+ if (template_copy == NULL) {
+
+ errno = ENOMEM;
+ goto err_mkhtemp;
+ }
+
+ /* we work on a cached copy first,
+ * to avoid partial writes of the
+ * input under fault conditions
+ */
+ memcpy(template_copy, template, len + 1);
+
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0))
+
+ /* redundant copy, reduce chance of
+ * mangling (regression mitigation)
+ */
+
+ fname_copy = malloc(fname_len + 1);
+
+ if (fname_copy == NULL) {
+
+ errno = ENOMEM;
+ goto err_mkhtemp;
+ }
+
+ memcpy(fname_copy,
+ template_copy + len - fname_len,
+ fname_len + 1);
+
+ p = fname_copy + fname_len - xc;
+#else
+ p = template_copy + len - xc;
+#endif
+
+ for (retries = 0; retries < max_retries; retries++) {
+
+ for (chx = 0; chx < xc; chx++) {
+
+ /* clamp rand to prevent modulo bias
+ * (reduced risk of entropy leak)
+ */
+
+ do {
+ saved_rand_error = errno;
+
+ rand_failures = 0;
+retry_rand:
+ errno = 0;
+
+ /* on bsd: uses arc4random
+ on linux: uses getrandom
+ on OLD linux: /dev/urandom
+ on old/other unix: /dev/urandom
+ */
+ r = rlong();
+
+ if (errno > 0) {
+
+ if (++rand_failures <= 8)
+ goto retry_rand;
+
+ goto err_mkhtemp;
+ }
+
+ rand_failures = 0;
+
+ errno = saved_rand_error;
+
+ } while (r >= limit);
+
+ p[chx] = ch[r % (sizeof(ch) - 1)];
+ }
+
+#if defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0) /* openat(2) added to linux in 2006, BSDs later
+ */
+ *fd = open(template_copy,
+ O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC |
+ O_NOCTTY, 0600);
+#else
+ /*
+ * use the file descriptor instead.
+ * (danach prüfen wir die Sicherheit des Verzeichnisses)
+ */
+ if (fstat(dirfd, &st_dir_now) < 0)
+ goto err_mkhtemp;
+ /*
+ * mitigate symlink attacks before open
+ */
+ if (st_dir_now.st_dev != st_dir_initial->st_dev ||
+ st_dir_now.st_ino != st_dir_initial->st_ino) {
+ errno = ESTALE;
+ goto err_mkhtemp;
+ }
+
+ *fd = openat(dirfd, fname_copy,
+ O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC |
+ O_NOCTTY, 0600);
+#endif
+
+ if (*fd >= 0) {
+
+ file_created = 1;
+
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0))
+
+ memcpy(template_copy + len - fname_len,
+ fname_copy, fname_len);
+#endif
+
+ if (secure_file(fd, st, O_APPEND, 1, 1, 0600) < 0)
+ goto err_mkhtemp;
+
+ /* copy replaced characters
+ */
+ memcpy(
+ template + len + xc,
+ template_copy + len + xc,
+ xc);
+
+ if (template_copy != NULL) {
+
+ free(template_copy);
+ template_copy = NULL;
+ }
+
+ errno = saved_errno;
+
+ /* thunder room secure
+ */
+ return *fd;
+ }
+
+ if (errno != EEXIST &&
+ errno != EINTR &&
+ errno != EAGAIN)
+ goto err_mkhtemp;
+ }
+
+err_mkhtemp:
+
+ saved_errno = errno;
+
+ if (*fd >= 0) {
+
+ (void) close_on_eintr(*fd);
+
+ *fd = -1;
+ /* ^^^^^ the caller gives us a dir,
+ for use and we write in it
+ but *we* create their file
+
+ touch their *fd, not dirfd
+ as we only initialised *fd
+ */
+ }
+
+ if (template_copy != NULL) {
+
+ /* we created it, so *we* nuke it
+ */
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0))
+
+ if (file_created &&
+ fname_copy != NULL)
+ (void) unlinkat(dirfd, fname_copy, 0);
+#else
+ if (file_created)
+ (void) unlink(template_copy);
+
+#endif
+
+ free(template_copy);
+
+ template_copy = NULL;
+ }
+
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0))
+
+ if (fname_copy != NULL) {
+
+ free(fname_copy);
+ fname_copy = NULL;
+ }
+
+#endif
+
+ errno = saved_errno;
+
+ /* returning EINTR/EAGAIN
+ ourselves means that a
+ caller could implement
+ the same wait loop
+ */
+ if (errno != EEXIST &&
+ errno != EINTR &&
+ errno != EAGAIN) {
+
+ if (errno == saved_errno)
+ errno = ECANCELED;
+ }
+
+ return -1;
+}
+
+/* split up a given
+ * path into directory
+ * and file name. used
+ * for e.g. openat
+ */
+int
+split_path(const char *path,
+ struct path_split *ps)
+{
+ size_t maxlen;
+ size_t len;
+
+ char *slash;
+
+ int saved_errno = errno;
+
+ if (path == NULL || ps == NULL)
+ goto err_split_path;
+
+#if defined(PATH_LEN) && \
+(PATH_LEN) >= 256
+ maxlen = PATH_LEN;
+#else
+ maxlen = 4096;
+#endif
+
+ if (slen(path, maxlen, &len) < 0)
+ goto err_split_path;
+
+ if (len == 0 || len >= maxlen) {
+
+ errno = ERANGE;
+ goto err_split_path;
+ }
+
+ ps->buf = malloc(len + 1);
+ if (ps->buf == NULL) {
+
+ errno = ENOMEM;
+ goto err_split_path;
+ }
+
+ memcpy(ps->buf, path, len + 1);
+
+ for ( ; len > 1 &&
+ ps->buf[len - 1] == '/'; len--)
+ ps->buf[len - 1] = '\0';
+
+ slash = strrchr(ps->buf, '/');
+
+ if (slash) {
+
+ *slash = '\0';
+ ps->base = slash + 1;
+
+ if (*ps->buf == '\0') {
+
+ ps->buf[0] = '/';
+ ps->buf[1] = '\0';
+ }
+ } else {
+
+ ps->base = ps->buf;
+
+ ps->buf[0] = '.';
+ ps->buf[1] = '\0';
+ }
+
+ ps->dirfd = open_verified_dir(ps->buf);
+ if (ps->dirfd < 0)
+ goto err_split_path;
+
+ errno = saved_errno;
+
+ return 0;
+
+err_split_path:
+
+ saved_errno = errno;
+
+ if (ps->buf != NULL) {
+ free(ps->buf);
+ ps->buf = NULL;
+ }
+
+ if (ps->dirfd >= 0) {
+ (void) close_on_eintr(ps->dirfd);
+ ps->dirfd = -1;
+ }
+
+ errno = saved_errno;
+
+ if (errno == saved_errno)
+ errno = EIO; /* likely open/check_dirfd */
+
+ return -1;
+}
+
+/* when we open a directory,
+ * we need to secure it each
+ * time against replacement
+ * attacks (e.g. symlinks)
+ */
+int
+open_verified_dir(const char *path)
+{
+ int fd;
+ int saved_errno = errno;
+
+ fd = open(path, O_RDONLY | O_DIRECTORY |
+ O_CLOEXEC | O_NOCTTY
+#ifdef O_NOFOLLOW
+ | O_NOFOLLOW
+#endif
+ );
+
+ if (fd < 0)
return -1;
+
+ errno = saved_errno;
+
+ if (check_dirfd(fd, path) < 0) {
+
+ saved_errno = errno;
+ (void) close_on_eintr(fd);
+
+ errno = saved_errno;
+ return -1;
+ }
+
+ errno = saved_errno;
+ return fd;
+}
+
+/* useful for mitigating directory
+ * replacement attacks; call this
+ * before e.g. stat(), right after
+ * calling open/openat(). compares
+ * the inode/device of a given path
+ * relative to the file descriptor,
+ * which if changed would indicate
+ * a possible attack / race condition
+ */
+int
+check_dirfd(int dirfd, const char *path)
+{
+ struct stat st_fd;
+ struct stat st_path;
+
+ int saved_errno = errno;
+
+ if (dirfd < 0) {
+ errno = EBADF;
+ goto err_check_dirfd;
+ }
+
+ if (path == NULL) {
+ errno = EFAULT;
+ goto err_check_dirfd;
+ }
+
+ if (fstat(dirfd, &st_fd) < 0)
+ goto err_check_dirfd;
+
+#if !(defined(DISABLE_OPENAT) && \
+ ((DISABLE_OPENAT) > 0))
+ /*
+ * mitigate symlink / directory replacement
+ * attacks (fstatat added to linux in 2006,
+ * and the BSDs added it later on)
+ *
+ * on older/weird unix, you'll see stat(2),
+ * and would therefore be vulnerable.
+ */
+ if (fstatat(AT_FDCWD, path, &st_path,
+ AT_SYMLINK_NOFOLLOW) != 0)
+ goto err_check_dirfd;
+#else
+ if (stat(path, &st_path) != 0)
+ goto err_check_dirfd;
+#endif
+
+ if (st_fd.st_dev != st_path.st_dev ||
+ st_fd.st_ino != st_path.st_ino) {
+
+ errno = ENOENT;
+ goto err_check_dirfd;
+ }
+
+ errno = saved_errno;
+
+ return 0;
+
+err_check_dirfd:
+
+ if (errno == saved_errno)
+ errno = EPERM; /* context: symlink attack */
+
+ return -1;
+}
+
+/* why doesn't literally
+ every libc have this?
+
+ TODO: consider set_flags,
+ complementing bad_flags,
+ for setting new flags;
+ with another option to
+ set it before the bad_flags
+ check, or after it (because
+ some callers may be setting
+ flags given to them with
+ theirs OR'd in, yet still
+ want to filter bad flags,
+ whereas others may not want
+ to modify flags on a file
+ that already contains
+ specific flags)
+ */
+int
+secure_file(int *fd,
+ struct stat *st, int bad_flags,
+ int check_seek, int do_lock,
+ mode_t mode)
+{
+ int flags;
+ int saved_errno = errno;
+
+ if (fd == NULL) {
+ errno = EFAULT;
+ goto err_secure_file;
+ }
+ if (*fd < 0) {
+ errno = EBADF;
+ goto err_secure_file;
}
- for (i = 0; i < 200; i++) {
+ if (st == NULL) {
+ errno = EFAULT;
+ goto err_secure_file;
+ }
- for (j = 0; j < xc; j++) {
+ flags = fcntl(*fd, F_GETFL);
- r = rlong();
+ if (flags == -1)
+ goto err_secure_file;
+
+ if (bad_flags > 0) {
- p[j] = ch[(unsigned long)(r >> 1) % (sizeof(ch) - 1)];
+ /*
+ * For example:
+ * O_APPEND would permit offsets
+ * to be ignored, which breaks
+ * positional read/write
+ *
+ * You might provide that as
+ * the mask to this function,
+ * before doing positional i/o
+ */
+ if (flags & bad_flags) {
+ errno = EPERM;
+ goto err_secure_file;
}
+ }
- fd = open(template,
- O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, 0600);
+ if (fstat(*fd, st) == -1)
+ goto err_secure_file;
- if (fd >= 0)
- return fd;
+ /*
+ * Extremely defensive
+ * likely pointless checks
+ */
+
+ /* check if it's a file */
+ if (!S_ISREG(st->st_mode)) {
+ errno = EBADF;
+ goto err_secure_file;
+ }
+
+ if (check_seek) {
+
+ /* check if it's seekable
+ */
+ if (lseek(*fd, 0, SEEK_CUR) == (off_t)-1)
+ goto err_secure_file;
+ }
- if (errno != EEXIST)
- return -1;
+ /* tmpfile re-linked, or
+ unlinked, while opened
+ */
+ if (st->st_nlink != 1) {
+ errno = ELOOP;
+ goto err_secure_file;
}
- errno = EEXIST;
+ /* block if we don't own the file
+ * (exception made for root)
+ */
+ if (st->st_uid != geteuid() && /* someone else's file */
+ geteuid() != 0) { /* override for root */
+
+ errno = EPERM;
+ goto err_secure_file;
+ }
+ if (is_owner(st) < 0)
+ goto err_secure_file;
+
+ /* world-writeable or group-ownership.
+ * if these are set, then others could
+ * modify the file (not secure)
+ */
+ if (st->st_mode & (S_IWGRP | S_IWOTH)) {
+ errno = EPERM;
+ goto err_secure_file;
+ }
+
+ if (do_lock) {
+ if (lock_file(*fd, flags) == -1)
+ goto err_secure_file;
+ }
+
+ if (fchmod(*fd, mode) == -1)
+ goto err_secure_file;
+
+ errno = saved_errno;
+ return 0;
+
+err_secure_file:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
+ return -1;
+}
+
+int
+is_owner(struct stat *st)
+{
+ if (st == NULL) {
+
+ errno = EFAULT;
+ return -1;
+ }
+
+#if ALLOW_ROOT_OVERRIDE
+ if (st->st_uid != geteuid() && /* someone else's file */
+ geteuid() != 0) { /* override for root */
+#else
+ if (st->st_uid != geteuid()) { /* someone else's file */
+#endif /* and no root override */
+ errno = EPERM;
+ return -1;
+ }
+
+ return 0;
+}
+
+int
+lock_file(int fd, int flags)
+{
+ struct flock fl;
+ int saved_errno = errno;
+
+ if (fd < 0) {
+ errno = EBADF;
+ goto err_lock_file;
+ }
+
+ if (flags < 0) {
+ errno = EINVAL;
+ goto err_lock_file;
+ }
+
+ memset(&fl, 0, sizeof(fl));
+
+ if ((flags & O_ACCMODE) == O_RDONLY)
+ fl.l_type = F_RDLCK;
+ else
+ fl.l_type = F_WRLCK;
+
+ fl.l_whence = SEEK_SET;
+
+ if (fcntl(fd, F_SETLK, &fl) == -1)
+ goto err_lock_file;
+
+ saved_errno = errno;
+ return 0;
+
+err_lock_file:
+
+ if (errno == saved_errno)
+ errno = EIO;
+
return -1;
}
@@ -493,21 +1638,23 @@ mkstemp_n(char *template)
* otherwise it will return an error.
*/
-long
-rw_file_exact(int fd, unsigned char *mem, unsigned long nrw,
+ssize_t
+rw_file_exact(int fd, unsigned char *mem, size_t nrw,
off_t off, int rw_type, int loop_eagain,
- int loop_eintr, unsigned long max_retries,
+ int loop_eintr, size_t max_retries,
int off_reset)
{
- long rval;
- long rc;
+ ssize_t rval;
+ ssize_t rc;
- unsigned long nrw_cur;
+ size_t nrw_cur;
off_t off_cur;
void *mem_cur;
- unsigned long retries_on_zero;
+ size_t retries_on_zero;
+
+ int saved_errno = errno;
rval = 0;
@@ -515,22 +1662,28 @@ rw_file_exact(int fd, unsigned char *mem, unsigned long nrw,
retries_on_zero = 0;
if (io_args(fd, mem, nrw, off, rw_type) == -1)
- return -1;
+ goto err_rw_file_exact;
while (1) {
/* Prevent theoretical overflow */
- if (rval >= 0 && (unsigned long)rval > (nrw - rc))
+ if (rval >= 0 && (size_t)rval > (nrw - rc)) {
+ errno = EOVERFLOW;
goto err_rw_file_exact;
+ }
rc += rval;
- if ((unsigned long)rc >= nrw)
+ if ((size_t)rc >= nrw)
break;
- mem_cur = (void *)(mem + (unsigned long)rc);
- nrw_cur = (unsigned long)(nrw - (unsigned long)rc);
- if (off < 0)
+ mem_cur = (void *)(mem + (size_t)rc);
+ nrw_cur = (size_t)(nrw - (size_t)rc);
+
+ if (off < 0) {
+ errno = EOVERFLOW;
goto err_rw_file_exact;
+ }
+
off_cur = off + (off_t)rc;
rval = prw(fd, mem_cur, nrw_cur, off_cur,
@@ -538,24 +1691,38 @@ rw_file_exact(int fd, unsigned char *mem, unsigned long nrw,
off_reset);
if (rval < 0)
- return -1;
+ goto err_rw_file_exact;
if (rval == 0) {
if (retries_on_zero++ < max_retries)
continue;
+
+ errno = EIO;
goto err_rw_file_exact;
}
retries_on_zero = 0;
}
- if ((unsigned long)rc != nrw)
+ if ((size_t)rc != nrw) {
+
+ errno = EIO;
goto err_rw_file_exact;
+ }
+
+ rval = rw_over_nrw(rc, nrw);
+ if (rval < 0)
+ goto err_rw_file_exact;
+
+ errno = saved_errno;
- return rw_over_nrw(rc, nrw);
+ return rval;
err_rw_file_exact:
- errno = EIO;
+
+ if (errno == saved_errno)
+ errno = EIO;
+
return -1;
}
@@ -569,7 +1736,7 @@ err_rw_file_exact:
* mitigate race conditions on file descriptors
*
* If you need real pwrite/pread, just compile
- * with flag: HAVE_REAL_PREAD_PWRITE=1
+ * with flag: REAL_POS_IO=1
*
* A fallback is provided for regular read/write.
* rw_type can be IO_READ (read), IO_WRITE (write),
@@ -587,27 +1754,27 @@ err_rw_file_exact:
* off_reset 0: never reset if changed
*/
-long
-prw(int fd, void *mem, unsigned long nrw,
+ssize_t
+prw(int fd, void *mem, size_t nrw,
off_t off, int rw_type,
int loop_eagain, int loop_eintr,
int off_reset)
{
- long r;
+ ssize_t rval;
+ ssize_t r;
int positional_rw;
struct stat st;
-#if !defined(HAVE_REAL_PREAD_PWRITE) || \
- HAVE_REAL_PREAD_PWRITE < 1
- int saved_errno;
+#if !defined(REAL_POS_IO) || \
+ REAL_POS_IO < 1
off_t verified;
off_t off_orig;
off_t off_last;
#endif
+ int saved_errno = errno;
if (io_args(fd, mem, nrw, off, rw_type)
- == -1) {
- return -1;
- }
+ == -1)
+ goto err_prw;
r = -1;
@@ -621,7 +1788,7 @@ prw(int fd, void *mem, unsigned long nrw,
*/
if (check_file(fd, &st) == -1)
- return -1;
+ goto err_prw;
}
if (rw_type >= IO_PREAD)
@@ -632,16 +1799,16 @@ prw(int fd, void *mem, unsigned long nrw,
try_rw_again:
if (!positional_rw) {
-#if defined(HAVE_REAL_PREAD_PWRITE) && \
- HAVE_REAL_PREAD_PWRITE > 0
+#if defined(REAL_POS_IO) && \
+ REAL_POS_IO > 0
real_pread_pwrite:
#endif
if (rw_type == IO_WRITE)
r = write(fd, mem, nrw);
else if (rw_type == IO_READ)
r = read(fd, mem, nrw);
-#if defined(HAVE_REAL_PREAD_PWRITE) && \
- HAVE_REAL_PREAD_PWRITE > 0
+#if defined(REAL_POS_IO) && \
+ REAL_POS_IO > 0
else if (rw_type == IO_PWRITE)
r = pwrite(fd, mem, nrw, off);
else if (rw_type == IO_PREAD)
@@ -652,11 +1819,17 @@ real_pread_pwrite:
|| errno == try_err(loop_eagain, EAGAIN)))
goto try_rw_again;
- return rw_over_nrw(r, nrw);
+ rval = rw_over_nrw(r, nrw);
+ if (rval < 0)
+ goto err_prw;
+
+ errno = saved_errno;
+
+ return rval;
}
-#if defined(HAVE_REAL_PREAD_PWRITE) && \
- HAVE_REAL_PREAD_PWRITE > 0
+#if defined(REAL_POS_IO) && \
+ REAL_POS_IO > 0
goto real_pread_pwrite;
#else
if ((off_orig = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
@@ -687,18 +1860,19 @@ real_pread_pwrite:
verified = lseek_on_eintr(fd, (off_t)0, SEEK_CUR,
loop_eagain, loop_eintr);
- if (off != verified)
+ if (off != verified) {
+
+ errno = EBUSY;
goto err_prw;
+ }
if (rw_type == IO_PREAD)
r = read(fd, mem, nrw);
else if (rw_type == IO_PWRITE)
r = write(fd, mem, nrw);
- if (rw_over_nrw(r, nrw) == -1) {
- errno = EIO;
+ if (rw_over_nrw(r, nrw) == -1)
break;
- }
} while (r == -1 &&
(errno == try_err(loop_eintr, EINTR) ||
@@ -717,65 +1891,110 @@ real_pread_pwrite:
errno = saved_errno;
- return rw_over_nrw(r, nrw);
+ rval = rw_over_nrw(r, nrw);
+ if (rval < 0)
+ goto err_prw;
+
+ errno = saved_errno;
+
+ return rval;
+
#endif
err_prw:
- errno = EIO;
+
+ if (errno == saved_errno)
+ errno = EIO;
+
return -1;
}
int
-io_args(int fd, void *mem, unsigned long nrw,
+io_args(int fd, void *mem, size_t nrw,
off_t off, int rw_type)
{
+ int saved_errno = errno;
+
/* obviously */
- if (mem == NULL)
+ if (mem == NULL) {
+
+ errno = EFAULT;
goto err_io_args;
+ }
/* uninitialised fd */
- if (fd < 0)
+ if (fd < 0) {
+
+ errno = EBADF;
goto err_io_args;
+ }
/* negative offset */
- if (off < 0)
+ if (off < 0) {
+
+ errno = ERANGE;
goto err_io_args;
+ }
/* prevent zero-byte rw */
if (!nrw)
goto err_io_args;
/* prevent overflow */
- if (nrw > (unsigned long)X_LONG_MAX)
+ if (nrw > (size_t)SSIZE_MAX) {
+
+ errno = ERANGE;
goto err_io_args;
+ }
/* prevent overflow */
- if (((unsigned long)off + nrw) < (unsigned long)off)
+ if (((size_t)off + nrw) < (size_t)off) {
+
+ errno = ERANGE;
goto err_io_args;
+ }
- if (rw_type > IO_PWRITE)
+ if (rw_type > IO_PWRITE) {
+
+ errno = EINVAL;
goto err_io_args;
+ }
+
+ errno = saved_errno;
return 0;
err_io_args:
- errno = EIO;
+
+ if (errno == saved_errno)
+ errno = EINVAL;
+
return -1;
}
int
check_file(int fd, struct stat *st)
{
+ int saved_errno = errno;
+
if (fstat(fd, st) == -1)
goto err_is_file;
- if (!S_ISREG(st->st_mode))
+ if (!S_ISREG(st->st_mode)) {
+
+ errno = EBADF;
goto err_is_file;
+ }
+
+ errno = saved_errno;
return 0;
err_is_file:
- errno = EIO;
+
+ if (errno == saved_errno)
+ errno = EINVAL;
+
return -1;
}
@@ -783,9 +2002,11 @@ err_is_file:
* specification != implementation
*/
-long
-rw_over_nrw(long r, unsigned long nrw)
+ssize_t
+rw_over_nrw(ssize_t r, size_t nrw)
{
+ int saved_errno = errno;
+
/* not a libc bug, but we
* don't like the number zero
*/
@@ -795,8 +2016,8 @@ rw_over_nrw(long r, unsigned long nrw)
if (r == -1)
return r;
- if ((unsigned long)
- r > X_LONG_MAX) {
+ if ((size_t)
+ r > SSIZE_MAX) {
/* Theoretical buggy libc
* check. Extremely academic.
@@ -810,10 +2031,11 @@ rw_over_nrw(long r, unsigned long nrw)
* [p]read() or [p]write()
*
* NOTE: here, we assume
- * long integers are the
+ * ssize_t integers are the
* same size as SSIZE_T
*/
+ errno = ERANGE;
goto err_rw_over_nrw;
}
@@ -821,19 +2043,26 @@ rw_over_nrw(long r, unsigned long nrw)
* Should never return a number of
* bytes above the requested length.
*/
- if ((unsigned long)r > nrw)
+ if ((size_t)r > nrw) {
+
+ errno = ERANGE;
goto err_rw_over_nrw;
+ }
+
+ errno = saved_errno;
return r;
err_rw_over_nrw:
- errno = EIO;
+ if (errno == saved_errno)
+ errno = EIO;
+
return -1;
}
-#if !defined(HAVE_REAL_PREAD_PWRITE) || \
- HAVE_REAL_PREAD_PWRITE < 1
+#if !defined(REAL_POS_IO) || \
+ REAL_POS_IO < 1
off_t
lseek_on_eintr(int fd, off_t off, int whence,
int loop_eagain, int loop_eintr)
@@ -869,9 +2098,9 @@ close_on_eintr(int fd)
do {
r = close(fd);
- } while (r == -1 && errno == EINTR);
+ } while (r == -1 && (errno == EINTR || errno == EAGAIN));
- if (r > -1)
+ if (r >= 0)
errno = saved_errno;
return r;
@@ -881,10 +2110,14 @@ int
fsync_on_eintr(int fd)
{
int r;
+ int saved_errno = errno;
do {
r = fsync(fd);
- } while (r == -1 && errno == EINTR);
+ } while (r == -1 && (errno == EINTR || errno == EAGAIN));
+
+ if (r >= 0)
+ errno = saved_errno;
return r;
}