From c5e4afad488309b4e08064bbe97960e52846c50c Mon Sep 17 00:00:00 2001
From: monty <monty2@programmer.net>
Date: Mon, 8 May 2017 22:36:53 -0400
Subject: [PATCH] fixed buffer overflow in sqUnixFile.c using safer string
 functions based on BSD's strlcpy()/strlcat()

---
 platforms/unix/plugins/FilePlugin/sqUnixFile.c | 468 ++++++++++++-------------
 platforms/unix/vm/Makefile.in                  |   4 +-
 platforms/unix/vm/sqStrSafe.c                  | 107 ++++++
 platforms/unix/vm/sqStrSafe.h                  |  19 +
 4 files changed, 360 insertions(+), 238 deletions(-)
 create mode 100644 platforms/unix/vm/sqStrSafe.c
 create mode 100644 platforms/unix/vm/sqStrSafe.h

diff --git a/platforms/unix/plugins/FilePlugin/sqUnixFile.c b/platforms/unix/plugins/FilePlugin/sqUnixFile.c
index 14cd571d1..79f85770d 100644
--- a/platforms/unix/plugins/FilePlugin/sqUnixFile.c
+++ b/platforms/unix/plugins/FilePlugin/sqUnixFile.c
@@ -28,37 +28,21 @@
 /* Author: Ian.Piumarta@INRIA.Fr
  */
 
-#include "sq.h"
-#include "FilePlugin.h"
-#include "sqUnixCharConv.h"
-
-#ifdef HAVE_DIRENT_H
-# include <dirent.h>
-# define NAMLEN(dirent) strlen((dirent)->d_name)
-#else
-# define dirent direct
-# define NAMLEN(dirent) (dirent)->d_namlen
-# ifdef HAVE_SYS_NDIR_H
-#  include <sys/ndir.h>
-# endif
-# ifdef HAVE_SYS_DIR_H
-#  include <sys/dir.h>
-# endif
-# ifdef HAVE_NDIR_H
-#  include <ndir.h>
-# endif
-#endif
-
-#ifdef HAVE_UNISTD_H
-# include <sys/types.h>
-# include <unistd.h>
-#endif
+#include <sys/stat.h>
+#include <sys/types.h>
 
-#include <time.h>
+#include <dirent.h>
 #include <errno.h>
+#include <limits.h>
+#include <stdio.h>
 #include <string.h>
-#include <sys/param.h>
-#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "sq.h"
+#include "sqStrSafe.h"
+#include "sqUnixCharConv.h"
+#include "FilePlugin.h" /* must be included after sq.h */
 
 /***
 	The interface to the directory primitive is path based.
@@ -77,277 +61,289 @@
 #define DELIMITER '/'
 
 /*** Variables ***/
-char lastPath[MAXPATHLEN+1];
-int  lastPathValid = false;
-int  lastIndex= -1;
-DIR *openDir= 0;
-
+static char lastPath[PATH_MAX];
+static int lastPathValid = false;
+static int lastIndex = -1;
+static DIR *openDir = NULL;
 
 /*** Functions ***/
 
 extern time_t convertToSqueakTime(time_t unixTime);
 
-
 sqInt dir_Create(char *pathString, sqInt pathStringLength)
 {
-  /* Create a new directory with the given path. By default, this
-     directory is created relative to the cwd. */
-  char name[MAXPATHLEN+1];
-  int i;
-  if (pathStringLength >= MAXPATHLEN)
-    return false;
-  if (!sq2uxPath(pathString, pathStringLength, name, MAXPATHLEN, 1))
-    return false;
-  return mkdir(name, 0777) == 0;	/* rwxrwxrwx & ~umask */
-}
+	/* Create a new directory with the given path. By default, this
+	   directory is created relative to the cwd.
+	*/
+	char unixPath[PATH_MAX];
+
+	if (pathStringLength >= sizeof(unixPath))
+		return false;
+	if (sq2uxPath(pathString, pathStringLength, unixPath, sizeof(unixPath) - 1, 1) == 0)
+		return false;
 
+	return mkdir(unixPath, 0777) == 0;	/* rwxrwxrwx & ~umask */
+}
 
 sqInt dir_Delete(char *pathString, sqInt pathStringLength)
 {
-  /* Delete the existing directory with the given path. */
-  char name[MAXPATHLEN+1];
-  int i;
-  if (pathStringLength >= MAXPATHLEN)
-    return false;
-  if (!sq2uxPath(pathString, pathStringLength, name, MAXPATHLEN, 1))
-    return false;
-  if (lastPathValid && !strcmp(lastPath, name))
-    {
-      closedir(openDir);
-      lastPathValid= false;
-      lastIndex= -1;
-      lastPath[0]= '\0';
-    }
-  return rmdir(name) == 0;
+	/* Delete the existing directory with the given path. */
+	char unixPath[PATH_MAX];
+
+	if (pathStringLength >= sizeof(unixPath))
+		return false;
+	if (sq2uxPath(pathString, pathStringLength, unixPath, sizeof(unixPath) - 1, 1) == 0)
+		return false;
+
+	if (lastPathValid && strcmp(lastPath, unixPath) == 0) {
+		closedir(openDir);
+		lastPathValid = false;
+		lastIndex = -1;
+		strSafeCpy(lastPath, "", sizeof(lastPath));
+	}
+	return rmdir(unixPath) == 0;
 }
 
-
 sqInt dir_Delimitor(void)
 {
-  return DELIMITER;
+	return DELIMITER;
 }
 
-
 static int maybeOpenDir(char *unixPath)
 {
-  /* if the last opendir was to the same directory, re-use the directory
-     pointer from last time.  Otherwise close the previous directory,
-     open the new one, and save its name.  Return true if the operation
-     was successful, false if not. */
-  if (!lastPathValid || strcmp(lastPath, unixPath))
-    {
-      /* invalidate the old, open the new */
-      if (lastPathValid)
-	closedir(openDir);
-      lastPathValid= false;
-      strcpy(lastPath, unixPath);
-      if ((openDir= opendir(unixPath)) == 0)
-	return false;
-      lastPathValid= true;
-      lastIndex= 0;	/* first entry is index 1 */
-    }
-  return true;
+	/* If the last opendir was to the same directory, re-use the directory
+	   pointer from last time.  Otherwise close the previous directory,
+	   open the new one, and save its name.  Return true if the operation
+	   was successful, false if not.
+	*/
+	if (!lastPathValid || strcmp(lastPath, unixPath) != 0) {
+		/* invalidate the old, open the new */
+		if (lastPathValid)
+			closedir(openDir);
+		lastPathValid = false;
+		if (strSafeCpy(lastPath, unixPath, sizeof(lastPath)) >= sizeof(lastPath))
+			return false;
+		openDir = opendir(unixPath);
+		if (openDir == NULL)
+			return false;
+		lastPathValid = true;
+		lastIndex = 0;	/* first entry is index 1 */
+	}
+	return true;
 }
 
 #if PharoVM
 sqInt dir_Lookup(char *pathString, sqInt pathStringLength, sqInt index,
-/* outputs: */  char *name, sqInt *nameLength, sqInt *creationDate, sqInt *modificationDate,
-		sqInt *isDirectory, squeakFileOffsetType *sizeIfFile, sqInt * posixPermissions, sqInt *isSymlink)
-#else 
+/* outputs: */ char *name, sqInt * nameLength,
+		 sqInt * creationDate, sqInt * modificationDate,
+		 sqInt * isDirectory, squeakFileOffsetType * sizeIfFile,
+		 sqInt * posixPermissions, sqInt * isSymlink)
+#else
 sqInt dir_Lookup(char *pathString, sqInt pathStringLength, sqInt index,
-/* outputs: */  char *name, sqInt *nameLength, sqInt *creationDate, sqInt *modificationDate,
-		sqInt *isDirectory, squeakFileOffsetType *sizeIfFile)
+/* outputs: */ char *name, sqInt * nameLength,
+		 sqInt * creationDate, sqInt * modificationDate,
+		 sqInt * isDirectory, squeakFileOffsetType * sizeIfFile)
 #endif
 {
-  /* Lookup the index-th entry of the directory with the given path, starting
-     at the root of the file system. Set the name, name length, creation date,
-     creation time, directory flag, and file size (if the entry is a file).
-     Return:	0 	if a entry is found at the given index
-     		1	if the directory has fewer than index entries
-		2	if the given path has bad syntax or does not reach a directory
-  */
-
-  int i;
-  int nameLen= 0;
-  struct dirent *dirEntry= 0;
-  char unixPath[MAXPATHLEN+1];
-  struct stat statBuf;
-
-  /* default return values */
-  *name             = 0;
-  *nameLength       = 0;
-  *creationDate     = 0;
-  *modificationDate = 0;
-  *isDirectory      = false;
-  *sizeIfFile       = 0;
+	/* Lookup the index-th entry of the directory with the given path, starting
+	   at the root of the file system. Set the name, name length, creation date,
+	   creation time, directory flag, and file size (if the entry is a file).
+	   Return:    0       if a entry is found at the given index
+	   1  if the directory has fewer than index entries
+	   2  if the given path has bad syntax or does not reach a directory
+	 */
+
+	char unixPath[PATH_MAX];
+	struct dirent *dirEntry = NULL;
+	struct stat statBuf;
+	int i;
+
+	/* default return values */
+	strSafeCpy(name, "", NAME_MAX);
+	*nameLength = 0;
+	*creationDate = 0;
+	*modificationDate = 0;
+	*isDirectory = false;
+	*sizeIfFile = 0;
 #if PharoVM
-  *posixPermissions = 0;
-  *isSymlink        = false;
+	*posixPermissions = 0;
+	*isSymlink = false;
 #endif
 
-  if ((pathStringLength == 0))
-    strcpy(unixPath, ".");
-  else if (!sq2uxPath(pathString, pathStringLength, unixPath, MAXPATHLEN, 1))
-    return BAD_PATH;
-
-  /* get file or directory info */
-  if (!maybeOpenDir(unixPath))
-    return BAD_PATH;
-
-  if (++lastIndex == index)
-    index= 1;		/* fake that the dir is rewound and we want the first entry */
-  else
-    {
-      rewinddir(openDir);	/* really rewind it, and read to the index */
-      lastIndex= index;
-    }
-
-  for (i= 0; i < index; i++)
-    {
-    nextEntry:
-      do
-	{ 
-	  errno= 0; 
-	  dirEntry= readdir(openDir);
+	if (pathStringLength == 0) {
+		strSafeCpy(unixPath, ".", sizeof(unixPath));
+	} else {
+		if (pathStringLength >= sizeof(unixPath))
+			return BAD_PATH;
+		if (sq2uxPath(pathString, pathStringLength, unixPath, sizeof(unixPath) - 1, 1) == 0)
+			return BAD_PATH;
 	}
-      while ((dirEntry == 0) && (errno == EINTR));
-
-      if (!dirEntry)
-	return NO_MORE_ENTRIES;
-      
-      nameLen= NAMLEN(dirEntry);
-
-      /* ignore '.' and '..' (these are not *guaranteed* to be first) */
-      if (nameLen < 3 && dirEntry->d_name[0] == '.')
-	if (nameLen == 1 || dirEntry->d_name[1] == '.')
-	  goto nextEntry;
-    }
-
-  *nameLength= ux2sqPath(dirEntry->d_name, nameLen, name, MAXPATHLEN, 0);
-
-  {
-    char terminatedName[MAXPATHLEN];
-    strncpy(terminatedName, dirEntry->d_name, nameLen);
-    terminatedName[nameLen]= '\0';
-    strcat(unixPath, "/");
-    strcat(unixPath, terminatedName);
-    if (stat(unixPath, &statBuf) && lstat(unixPath, &statBuf))
-      {
-	/* We can't stat the entry, but failing here would invalidate
-	   the whole directory --bertf */
-	return ENTRY_FOUND;
-      }
-  }
 
-  /* last change time */
-  *creationDate= convertToSqueakTime(statBuf.st_ctime);
-  /* modification time */
-  *modificationDate= convertToSqueakTime(statBuf.st_mtime);
+	/* get file or directory info */
+	if (!maybeOpenDir(unixPath))
+		return BAD_PATH;
+
+	if (++lastIndex == index) {
+		index = 1;	/* fake that the dir is rewound and we want the first entry */
+	} else {
+		rewinddir(openDir);	/* really rewind it, and read to the index */
+		lastIndex = index;
+	}
 
-  if (S_ISDIR(statBuf.st_mode))
-    *isDirectory= true;
-  else
-    *sizeIfFile= statBuf.st_size;
+	i = 0;
+	while (i < index) {
+		do {
+			/* readdir() returns NULL on error or if it reaches
+			   the end of the dir and doesn't reset errno in the
+			   second case, so we must reset it before checking
+			   the return value and errno
+			 */
+			errno = 0;
+			dirEntry = readdir(openDir);
+		} while (dirEntry == NULL && errno == EINTR);
+
+		if (dirEntry == NULL)
+			return NO_MORE_ENTRIES;
+
+		/* ignore '.' and '..' (these are not *guaranteed* to be first) */
+		if (strcmp(dirEntry->d_name, ".") != 0
+			&& strcmp(dirEntry->d_name, "..") != 0)
+			++i;
+	}
+
+	if (strSafeCat(unixPath, "/", sizeof(unixPath)) >= sizeof(unixPath))
+		return BAD_PATH;
+	if (strSafeCat(unixPath, dirEntry->d_name, sizeof(unixPath)) >= sizeof(unixPath))
+		return BAD_PATH;
+
+	*nameLength = ux2sqPath(dirEntry->d_name, strlen(dirEntry->d_name), name, NAME_MAX - 1, 0);
+
+	if (stat(unixPath, &statBuf) != 0
+		&& lstat(unixPath, &statBuf) != 0) {
+		/* We can't stat the entry, but failing here would invalidate
+		   the whole directory --bertf */
+		return ENTRY_FOUND;
+	}
+
+	/* last change time */
+	*creationDate = convertToSqueakTime(statBuf.st_ctime);
+	/* modification time */
+	*modificationDate = convertToSqueakTime(statBuf.st_mtime);
+
+	if (S_ISDIR(statBuf.st_mode))
+		*isDirectory = true;
+	else
+		*sizeIfFile = statBuf.st_size;
 
 #if PharoVM
-  *isSymlink = S_ISLNK(statBuf.st_mode);
-  *posixPermissions = statBuf.st_mode & 0777;
+	*isSymlink = S_ISLNK(statBuf.st_mode);
+	*posixPermissions = statBuf.st_mode & 0777;
 #endif
 
-  return ENTRY_FOUND;
+	return ENTRY_FOUND;
 }
 
-
 #if PharoVM
-sqInt dir_EntryLookup(char *pathString, sqInt pathStringLength, char* nameString, sqInt nameStringLength,
-/* outputs: */  char *name, sqInt *nameLength, sqInt *creationDate, sqInt *modificationDate,
-		sqInt *isDirectory, squeakFileOffsetType *sizeIfFile, sqInt *posixPermissions, sqInt *isSymlink)
+sqInt dir_EntryLookup(char *pathString, sqInt pathStringLength,
+		      char *nameString, sqInt nameStringLength,
+/* outputs: */ char *name, sqInt * nameLength,
+		      sqInt * creationDate, sqInt * modificationDate,
+		      sqInt * isDirectory, squeakFileOffsetType * sizeIfFile,
+		      sqInt * posixPermissions, sqInt * isSymlink)
 #else
-sqInt dir_EntryLookup(char *pathString, sqInt pathStringLength, char* nameString, sqInt nameStringLength,
-/* outputs: */  char *name, sqInt *nameLength, sqInt *creationDate, sqInt *modificationDate,
-		sqInt *isDirectory, squeakFileOffsetType *sizeIfFile)
+sqInt dir_EntryLookup(char *pathString, sqInt pathStringLength,
+		      char *nameString, sqInt nameStringLength,
+/* outputs: */ char *name, sqInt * nameLength,
+		      sqInt * creationDate, sqInt * modificationDate,
+		      sqInt * isDirectory, squeakFileOffsetType * sizeIfFile)
 #endif
 {
-  /* Lookup the given name in the given directory,
-     Set the name, name length, creation date,
-     creation time, directory flag, and file size (if the entry is a file).
-     Return:	0 	if a entry is found at the given index
-     		1	if there is no such entry in the directory
-		2	if the given path has bad syntax or does not reach a directory
-  */
-  
-  char unixPath[MAXPATHLEN+1];
-  struct stat statBuf;
-
-  /* default return values */
-  *name             = 0;
-  *nameLength       = 0;
-  *creationDate     = 0;
-  *modificationDate = 0;
-  *isDirectory      = false;
-  *sizeIfFile       = 0;
+	/* Lookup the given name in the given directory,
+	   Set the name, name length, creation date,
+	   creation time, directory flag, and file size (if the entry is a file).
+	   Return:    0       if a entry is found at the given index
+	   1  if there is no such entry in the directory
+	   2  if the given path has bad syntax or does not reach a directory
+	 */
+
+	char unixPath[PATH_MAX];
+	char terminatedName[NAME_MAX];
+	struct stat statBuf;
+
+	/* default return values */
+	strSafeCpy(name, "", NAME_MAX);
+	*nameLength = 0;
+	*creationDate = 0;
+	*modificationDate = 0;
+	*isDirectory = false;
+	*sizeIfFile = 0;
 #if PharoVM
-  *posixPermissions = 0;
-  *isSymlink        = false;
+	*posixPermissions = 0;
+	*isSymlink = false;
 #endif
 
-  if ((pathStringLength == 0))
-    strcpy(unixPath, ".");
-  else if (!sq2uxPath(pathString, pathStringLength, unixPath, MAXPATHLEN, 1))
-    return BAD_PATH;
-
-  char terminatedName[MAXPATHLEN+1];
-  strncpy(terminatedName, nameString, nameStringLength);
-  terminatedName[nameStringLength]= '\0';
-  strcat(unixPath, "/");
-  strcat(unixPath, terminatedName);
-  if (stat(unixPath, &statBuf) && lstat(unixPath, &statBuf)) {
-	return NO_MORE_ENTRIES;
-  }
-
-  /* To match the results of dir_Lookup, copy back the file name */
-  *nameLength = ux2sqPath(nameString, nameStringLength, name, 256, 0);
-
-  /* last change time */
-  *creationDate= convertToSqueakTime(statBuf.st_ctime);
-  /* modification time */
-  *modificationDate= convertToSqueakTime(statBuf.st_mtime);
-
-  if (S_ISDIR(statBuf.st_mode))
-    *isDirectory= true;
-  else
-    *sizeIfFile= statBuf.st_size;
-  
+	if (pathStringLength == 0) {
+		strSafeCpy(unixPath, ".", sizeof(unixPath));
+	} else {
+		if (pathStringLength >= sizeof(unixPath))
+			return BAD_PATH;
+		if (sq2uxPath(pathString, pathStringLength, unixPath, sizeof(unixPath) - 1, 1) == 0)
+			return BAD_PATH;
+	}
+
+	if (strSafeCpyLen(terminatedName, nameString, nameStringLength, sizeof(terminatedName))
+		>= sizeof(terminatedName))
+		return BAD_PATH;
+	if (strSafeCat(unixPath, "/", sizeof(unixPath)) >= sizeof(unixPath))
+		return BAD_PATH;
+	if (strSafeCat(unixPath, terminatedName, sizeof(unixPath)) >= sizeof(unixPath))
+		return BAD_PATH;
+
+	if (stat(unixPath, &statBuf) != 0
+		&& lstat(unixPath, &statBuf) != 0)
+		return NO_MORE_ENTRIES;
+
+	/* To match the results of dir_Lookup(), copy back the file name */
+	*nameLength = ux2sqPath(nameString, nameStringLength, name, NAME_MAX - 1, 0);
+	/* last change time */
+	*creationDate = convertToSqueakTime(statBuf.st_ctime);
+	/* modification time */
+	*modificationDate = convertToSqueakTime(statBuf.st_mtime);
+
+	if (S_ISDIR(statBuf.st_mode))
+		*isDirectory = true;
+	else
+		*sizeIfFile = statBuf.st_size;
+
 #if PharoVM
-  *isSymlink = S_ISLNK(statBuf.st_mode);
-  *posixPermissions = statBuf.st_mode & 0777;
+	*isSymlink = S_ISLNK(statBuf.st_mode);
+	*posixPermissions = statBuf.st_mode & 0777;
 #endif
 
-  return ENTRY_FOUND;
+	return ENTRY_FOUND;
 }
 
 /* unix files are untyped, and the creator is correct by default */
 
-
-sqInt dir_SetMacFileTypeAndCreator(char *filename, sqInt filenameSize, char *fType, char *fCreator)
+sqInt dir_SetMacFileTypeAndCreator(char *filename, sqInt filenameSize,
+				   char *fType, char *fCreator)
 {
-  return true;
+	return true;
 }
 
-sqInt dir_GetMacFileTypeAndCreator(char *filename, sqInt filenameSize, char *fType, char *fCreator)
+sqInt dir_GetMacFileTypeAndCreator(char *filename, sqInt filenameSize,
+				   char *fType, char *fCreator)
 {
-  return true;
+	return true;
 }
 
-
 /*
  * The following is useful in a debugging context when the VM's output has been
  * directed to a log file.  It binds stdout to /dev/tty, arranging that output
  * of debugging print routines such as printOop appear on stdout.
  */
-void
-sqStdoutToDevTTY()
+void sqStdoutToDevTTY()
 {
-	if (!freopen("/dev/tty","w",stdout))
+	if (!freopen("/dev/tty", "w", stdout))
 		perror("sqStdoutToDevTTY freopen(\"/dev/tty\",\"w\",stdout):");
 }
diff --git a/platforms/unix/vm/Makefile.in b/platforms/unix/vm/Makefile.in
index a94637cd6..441cf4a3e 100644
--- a/platforms/unix/vm/Makefile.in
+++ b/platforms/unix/vm/Makefile.in
@@ -44,13 +44,13 @@ TARGET		= vm$a
 COBJS		= $(INTERP)$o cogit$o sqNamedPrims$o sqVirtualMachine$o sqHeapMap$o\
 			sqExternalSemaphores$o sqTicker$o aio$o debug$o osExports$o \
 			sqUnixExternalPrims$o sqUnixMemory$o sqUnixSpurMemory$o \
-			sqUnixCharConv$o sqUnixMain$o \
+			sqUnixCharConv$o sqStrSafe$o sqUnixMain$o \
 			sqUnixVMProfile$o sqUnixHeartbeat$o sqUnixThreads$o
 
 IOBJS		= $(INTERP)$o sqNamedPrims$o sqVirtualMachine$o sqHeapMap$o\
 			sqExternalSemaphores$o sqTicker$o aio$o debug$o osExports$o \
 			sqUnixExternalPrims$o sqUnixMemory$o sqUnixSpurMemory$o \
-			sqUnixCharConv$o sqUnixMain$o \
+			sqUnixCharConv$o sqStrSafe$o sqUnixMain$o \
 			sqUnixVMProfile$o sqUnixHeartbeat$o sqUnixThreads$o
 
 OBJS		= [COBJS_OR_IOBJS] # see mkmf
diff --git a/platforms/unix/vm/sqStrSafe.c b/platforms/unix/vm/sqStrSafe.c
new file mode 100644
index 000000000..2dda7f1bd
--- /dev/null
+++ b/platforms/unix/vm/sqStrSafe.c
@@ -0,0 +1,107 @@
+/*
+ * Safe(r) string functions adapted from BSD's strlcpy() and strlcat() functions.
+ */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <Todd.Miller@courtesan.com>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR 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 <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy string src to buffer dst of size dsize.  At most dsize-1
+ * chars will be copied.  Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+strSafeCpy(char *dst, const char *src, size_t dsize)
+{
+	const char *osrc = src;
+	size_t nleft = dsize;
+
+	/* Copy as many bytes as will fit. */
+	if (nleft != 0) {
+		while (--nleft != 0) {
+			if ((*dst++ = *src++) == '\0')
+				break;
+		}
+	}
+
+	/* Not enough room in dst, add NUL and traverse rest of src. */
+	if (nleft == 0) {
+		if (dsize != 0)
+			*dst = '\0';		/* NUL-terminate dst */
+		while (*src++)
+			;
+	}
+
+	return(src - osrc - 1);	/* count does not include NUL */
+}
+
+/*
+ * Appends src to string dst of size dsize (unlike strncat, dsize is the
+ * full size of dst, not space left).  At most dsize-1 characters
+ * will be copied.  Always NUL terminates (unless dsize <= strlen(dst)).
+ * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
+ * If retval >= siz, truncation occurred.
+ */
+size_t
+strSafeCat(char *dst, const char *src, size_t dsize)
+{
+	const char *odst = dst;
+	const char *osrc = src;
+	size_t n = dsize;
+	size_t dlen;
+
+	/* Find the end of dst and adjust bytes left but don't go past end. */
+	while (n-- != 0 && *dst != '\0')
+		dst++;
+	dlen = dst - odst;
+	n = dsize - dlen;
+
+	if (n-- == 0)
+		return(dlen + strlen(src));
+	while (*src != '\0') {
+		if (n != 0) {
+			*dst++ = *src;
+			n--;
+		}
+		src++;
+	}
+	*dst = '\0';
+
+	return(dlen + (src - osrc));	/* count does not include NUL */
+}
+
+/*
+ * Copy non-NUL terminated string src of length slen to buffer dst of size
+ * dsize.  At most dsize-1 chars will be copied.
+ * Always NUL terminates (unless dsize == 0).
+ * Returns slen; if retval >= dsize, truncation occurred.
+ */
+size_t
+strSafeCpyLen(char *dst, const char *src, size_t slen, size_t dsize)
+{
+	if (dsize > 0) {
+		size_t dlen = (slen >= dsize) ? dsize - 1 : slen;
+
+		memcpy(dst, src, dlen);
+		dst[dlen] = '\0';
+	}
+
+	return slen;	/* count does not include NUL */
+}
diff --git a/platforms/unix/vm/sqStrSafe.h b/platforms/unix/vm/sqStrSafe.h
new file mode 100644
index 000000000..1d54aea28
--- /dev/null
+++ b/platforms/unix/vm/sqStrSafe.h
@@ -0,0 +1,19 @@
+/*
+ * Safe(r) string functions adapted from BSD's strlcpy() and strlcat() functions.
+ */
+
+#ifndef SQ_STR_SAFE_H
+#define SQ_STR_SAFE_H
+
+#include <sys/types.h>
+
+extern size_t
+strSafeCpy(char *dst, const char *src, size_t dsize);
+
+extern size_t
+strSafeCat(char *dst, const char *src, size_t dsize);
+
+extern size_t
+strSafeCpyLen(char *dst, const char *src, size_t slen, size_t dsize);
+
+#endif
-- 
2.11.0

