Merge pull request #4255 from pks-t/pks/buffer-grow-errors

Buffer growing cleanups
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ca52db7..354ae3e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -296,6 +296,9 @@
 # Specify sha1 implementation
 IF (USE_SHA1DC)
 	ADD_DEFINITIONS(-DGIT_SHA1_COLLISIONDETECT)
+	ADD_DEFINITIONS(-DSHA1DC_NO_STANDARD_INCLUDES=1)
+	ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_SHA1_C=\"common.h\")
+	ADD_DEFINITIONS(-DSHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C=\"common.h\")
 	FILE(GLOB SRC_SHA1 src/hash/hash_collisiondetect.c src/hash/sha1dc/*)
 ELSEIF (WIN32 AND NOT MINGW)
 	ADD_DEFINITIONS(-DGIT_SHA1_WIN32)
@@ -706,9 +709,9 @@
 
 	ENABLE_TESTING()
 	IF (WINHTTP OR OPENSSL_FOUND OR SECURITY_FOUND)
-		ADD_TEST(libgit2_clar libgit2_clar -ionline)
+		ADD_TEST(libgit2_clar libgit2_clar -ionline -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style)
 	ELSE ()
-		ADD_TEST(libgit2_clar libgit2_clar -v)
+		ADD_TEST(libgit2_clar libgit2_clar -v -xclone::local::git_style_unc_paths -xclone::local::standard_unc_paths_are_written_git_style)
 	ENDIF ()
 
 	# Add a test target which runs the cred callback tests, to be
diff --git a/src/fileops.c b/src/fileops.c
index a0a2795..bda17f0 100644
--- a/src/fileops.c
+++ b/src/fileops.c
@@ -1155,9 +1155,13 @@
 
 int git_futils_fsync_parent(const char *path)
 {
-	char *parent = git_path_dirname(path);
-	int error = git_futils_fsync_dir(parent);
+	char *parent;
+	int error;
 
+	if ((parent = git_path_dirname(path)) == NULL)
+		return -1;
+
+	error = git_futils_fsync_dir(parent);
 	git__free(parent);
 	return error;
 }
diff --git a/src/hash/sha1dc/sha1.c b/src/hash/sha1dc/sha1.c
index 8d12b83..facea1b 100644
--- a/src/hash/sha1dc/sha1.c
+++ b/src/hash/sha1dc/sha1.c
@@ -5,31 +5,75 @@
 * https://opensource.org/licenses/MIT
 ***/
 
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
 #include <string.h>
 #include <memory.h>
 #include <stdio.h>
 #include <stdlib.h>
+#endif
+
+#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C
+#include SHA1DC_CUSTOM_INCLUDE_SHA1_C
+#endif
+
+#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT
+#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1
+#endif
 
 #include "sha1.h"
 #include "ubc_check.h"
 
 
-/* 
+/*
    Because Little-Endian architectures are most common,
-   we only set BIGENDIAN if one of these conditions is met.
+   we only set SHA1DC_BIGENDIAN if one of these conditions is met.
    Note that all MSFT platforms are little endian,
    so none of these will be defined under the MSC compiler.
    If you are compiling on a big endian platform and your compiler does not define one of these,
    you will have to add whatever macros your tool chain defines to indicate Big-Endianness.
  */
-#if (defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)) || \
-    (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__)) || \
-    defined(__BIG_ENDIAN__) || defined(__ARMEB__) || defined(__THUMBEB__) ||  defined(__AARCH64EB__) || \
-    defined(_MIPSEB) || defined(__MIPSEB) || defined(__MIPSEB__)
+#ifdef SHA1DC_BIGENDIAN
+#undef SHA1DC_BIGENDIAN
+#endif
 
-#define BIGENDIAN	(1)
+#if (defined(_BYTE_ORDER) || defined(__BYTE_ORDER) || defined(__BYTE_ORDER__))
 
-#endif /*ENDIANNESS SELECTION*/
+#if ((defined(_BYTE_ORDER) && (_BYTE_ORDER == _BIG_ENDIAN)) || \
+     (defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)) || \
+     (defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __BIG_ENDIAN__)) )
+#define SHA1DC_BIGENDIAN
+#endif
+
+#else
+
+#if (defined(_BIG_ENDIAN) || defined(__BIG_ENDIAN) || defined(__BIG_ENDIAN__) || \
+     defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \
+     defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || \
+     defined(__sparc))
+#define SHA1DC_BIGENDIAN
+#endif
+
+#endif
+
+#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN))
+#undef SHA1DC_BIGENDIAN
+#endif
+#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN))
+#define SHA1DC_BIGENDIAN
+#endif
+/*ENDIANNESS SELECTION*/
+
+#if (defined SHA1DC_FORCE_UNALIGNED_ACCESS || \
+     defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \
+     defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__)  || \
+     defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \
+     defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \
+     defined(__386) || defined(_M_X64) || defined(_M_AMD64))
+
+#define SHA1DC_ALLOW_UNALIGNED_ACCESS
+
+#endif /*UNALIGNMENT DETECTION*/
+
 
 #define rotate_right(x,n) (((x)>>(n))|((x)<<(32-(n))))
 #define rotate_left(x,n)  (((x)<<(n))|((x)>>(32-(n))))
@@ -39,11 +83,11 @@
 
 #define sha1_mix(W, t)  (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1))
 
-#if defined(BIGENDIAN)
+#ifdef SHA1DC_BIGENDIAN
 	#define sha1_load(m, t, temp)  { temp = m[t]; }
 #else
 	#define sha1_load(m, t, temp)  { temp = m[t]; sha1_bswap32(temp); }
-#endif /*define(BIGENDIAN)*/
+#endif
 
 #define sha1_store(W, t, x)	*(volatile uint32_t *)&W[t] = x
 
@@ -872,6 +916,11 @@
 	ihvout[0] = ihvin[0] + a; ihvout[1] = ihvin[1] + b; ihvout[2] = ihvin[2] + c; ihvout[3] = ihvin[3] + d; ihvout[4] = ihvin[4] + e; \
 }
 
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable: 4127)  /* Complier complains about the checks in the above macro being constant. */
+#endif
+
 #ifdef DOSTORESTATE0
 SHA1_RECOMPRESS(0)
 #endif
@@ -1192,6 +1241,10 @@
 SHA1_RECOMPRESS(79)
 #endif
 
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
 static void sha1_recompression_step(uint32_t step, uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5])
 {
 	switch (step)
@@ -1609,7 +1662,7 @@
 	unsigned i, j;
 	uint32_t ubc_dv_mask[DVMASKSIZE] = { 0xFFFFFFFF };
 	uint32_t ihvtmp[5];
-	
+
 	ctx->ihv1[0] = ctx->ihv[0];
 	ctx->ihv1[1] = ctx->ihv[1];
 	ctx->ihv1[2] = ctx->ihv[2];
@@ -1665,7 +1718,7 @@
 	ctx->ihv[3] = 0x10325476;
 	ctx->ihv[4] = 0xC3D2E1F0;
 	ctx->found_collision = 0;
-	ctx->safe_hash = 1;
+	ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT;
 	ctx->ubc_check = 1;
 	ctx->detect_coll = 1;
 	ctx->reduced_round_coll = 0;
@@ -1713,6 +1766,7 @@
 void SHA1DCUpdate(SHA1_CTX* ctx, const char* buf, size_t len)
 {
 	unsigned left, fill;
+
 	if (len == 0)
 		return;
 
@@ -1731,7 +1785,13 @@
 	while (len >= 64)
 	{
 		ctx->total += 64;
+
+#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS)
 		sha1_process(ctx, (uint32_t*)(buf));
+#else
+		memcpy(ctx->buffer, buf, 64);
+		sha1_process(ctx, (uint32_t*)(ctx->buffer));
+#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */
 		buf += 64;
 		len -= 64;
 	}
@@ -1790,3 +1850,7 @@
 	output[19] = (unsigned char)(ctx->ihv[4]);
 	return ctx->found_collision;
 }
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C
+#endif
diff --git a/src/hash/sha1dc/sha1.h b/src/hash/sha1dc/sha1.h
index e867724..1e4e94b 100644
--- a/src/hash/sha1dc/sha1.h
+++ b/src/hash/sha1dc/sha1.h
@@ -5,42 +5,37 @@
 * https://opensource.org/licenses/MIT
 ***/
 
+#ifndef SHA1DC_SHA1_H
+#define SHA1DC_SHA1_H
+
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
 #include <stdint.h>
+#endif
 
-/* uses SHA-1 message expansion to expand the first 16 words of W[] to 80 words */
-/* void sha1_message_expansion(uint32_t W[80]); */
-
-/* sha-1 compression function; first version takes a message block pre-parsed as 16 32-bit integers, second version takes an already expanded message) */
-/* void sha1_compression(uint32_t ihv[5], const uint32_t m[16]);
-void sha1_compression_W(uint32_t ihv[5], const uint32_t W[80]); */
-
-/* same as sha1_compression_W, but additionally store intermediate states */
+/* sha-1 compression function that takes an already expanded message, and additionally store intermediate states */
 /* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is defined in ubc_check.h */
 void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]);
 
 /*
-// function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5])
-// where 0 <= T < 80
-//       me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference)
-//       state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block
-// the function will return:
-//       ihvin: the reconstructed input chaining value
-//       ihvout: the reconstructed output chaining value
+// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]).
+// Where 0 <= T < 80
+//       me2 is an expanded message (the expansion of an original message block XOR'ed with a disturbance vector's message block difference.)
+//       state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression function while processing the original message block.
+// The function will return:
+//       ihvin: The reconstructed input chaining value.
+//       ihvout: The reconstructed output chaining value.
 */
 typedef void(*sha1_recompression_type)(uint32_t*, uint32_t*, const uint32_t*, const uint32_t*);
 
-/* table of sha1_recompression_step_0, ... , sha1_recompression_step_79 */
-/* extern sha1_recompression_type sha1_recompression_step[80];*/
-
-/* a callback function type that can be set to be called when a collision block has been found: */
+/* A callback function type that can be set to be called when a collision block has been found: */
 /* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */
 typedef void(*collision_block_callback)(uint64_t, const uint32_t*, const uint32_t*, const uint32_t*, const uint32_t*);
 
-/* the SHA-1 context */
+/* The SHA-1 context. */
 typedef struct {
 	uint64_t total;
 	uint32_t ihv[5];
@@ -59,30 +54,34 @@
 	uint32_t states[80][5];
 } SHA1_CTX;
 
-/* initialize SHA-1 context */
+/* Initialize SHA-1 context. */
 void SHA1DCInit(SHA1_CTX*);
 
 /*
-// function to enable safe SHA-1 hashing:
-// collision attacks are thwarted by hashing a detected near-collision block 3 times
-// think of it as extending SHA-1 from 80-steps to 240-steps for such blocks:
-//   the best collision attacks against SHA-1 have complexity about 2^60,
-//   thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would 2^180
-//   an attacker would be better off using a generic birthday search of complexity 2^80
-//
-// enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected
-// but it will result in a different SHA-1 hash for messages where a collision attack was detected
-// this will automatically invalidate SHA-1 based digital signature forgeries
-// enabled by default
+    Function to enable safe SHA-1 hashing:
+    Collision attacks are thwarted by hashing a detected near-collision block 3 times.
+    Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks:
+        The best collision attacks against SHA-1 have complexity about 2^60,
+        thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be 2^180.
+        An attacker would be better off using a generic birthday search of complexity 2^80.
+
+   Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no collision attack was detected,
+   but it will result in a different SHA-1 hash for messages where a collision attack was detected.
+   This will automatically invalidate SHA-1 based digital signature forgeries.
+   Enabled by default.
 */
 void SHA1DCSetSafeHash(SHA1_CTX*, int);
 
-/* function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up) */
-/* enabled by default */
+/*
+    Function to disable or enable the use of Unavoidable Bitconditions (provides a significant speed up).
+    Enabled by default
+ */
 void SHA1DCSetUseUBC(SHA1_CTX*, int);
 
-/* function to disable or enable the use of Collision Detection */
-/* enabled by default */
+/*
+    Function to disable or enable the use of Collision Detection.
+    Enabled by default.
+ */
 void SHA1DCSetUseDetectColl(SHA1_CTX*, int);
 
 /* function to disable or enable the detection of reduced-round SHA-1 collisions */
@@ -98,8 +97,14 @@
 
 /* obtain SHA-1 hash from SHA-1 context */
 /* returns: 0 = no collision detected, otherwise = collision found => warn user for active attack */
-int  SHA1DCFinal(unsigned char[20], SHA1_CTX*); 
+int  SHA1DCFinal(unsigned char[20], SHA1_CTX*);
 
 #if defined(__cplusplus)
 }
 #endif
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H
+#endif
+
+#endif
diff --git a/src/hash/sha1dc/ubc_check.c b/src/hash/sha1dc/ubc_check.c
index 27d0976..b3beff2 100644
--- a/src/hash/sha1dc/ubc_check.c
+++ b/src/hash/sha1dc/ubc_check.c
@@ -24,7 +24,12 @@
 // ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in the tools section
 */
 
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
 #include <stdint.h>
+#endif
+#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C
+#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C
+#endif
 #include "ubc_check.h"
 
 static const uint32_t DV_I_43_0_bit 	= (uint32_t)(1) << 0;
@@ -361,3 +366,7 @@
 
 	dvmask[0]=mask;
 }
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C
+#endif
diff --git a/src/hash/sha1dc/ubc_check.h b/src/hash/sha1dc/ubc_check.h
index b349bed..d7e17dc 100644
--- a/src/hash/sha1dc/ubc_check.h
+++ b/src/hash/sha1dc/ubc_check.h
@@ -20,14 +20,16 @@
 // thus one needs to do the recompression check for each DV that has its bit set
 */
 
-#ifndef UBC_CHECK_H
-#define UBC_CHECK_H
+#ifndef SHA1DC_UBC_CHECK_H
+#define SHA1DC_UBC_CHECK_H
 
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
 #include <stdint.h>
+#endif
 
 #define DVMASKSIZE 1
 typedef struct { int dvType; int dvK; int dvB; int testt; int maski; int maskb; uint32_t dm[80]; } dv_info_t;
@@ -43,4 +45,8 @@
 }
 #endif
 
-#endif /* UBC_CHECK_H */
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H
+#endif
+
+#endif
diff --git a/src/index.c b/src/index.c
index 932a530..c29e90f 100644
--- a/src/index.c
+++ b/src/index.c
@@ -54,10 +54,6 @@
 				  unsigned int flags,
 				  git_index_matched_path_cb cb, void *payload);
 
-#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7)
-#define short_entry_size(len) entry_size(struct entry_short, len)
-#define long_entry_size(len) entry_size(struct entry_long, len)
-
 #define minimal_entry_size (offsetof(struct entry_short, path))
 
 static const size_t INDEX_FOOTER_SIZE = GIT_OID_RAWSZ;
@@ -2282,12 +2278,29 @@
 	return 0;
 }
 
+static size_t index_entry_size(size_t path_len, size_t varint_len, uint32_t flags)
+{
+	if (varint_len) {
+		if (flags & GIT_IDXENTRY_EXTENDED)
+			return offsetof(struct entry_long, path) + path_len + 1 + varint_len;
+		else
+			return offsetof(struct entry_short, path) + path_len + 1 + varint_len;
+	} else {
+#define entry_size(type,len) ((offsetof(type, path) + (len) + 8) & ~7)
+		if (flags & GIT_IDXENTRY_EXTENDED)
+			return entry_size(struct entry_long, path_len);
+		else
+			return entry_size(struct entry_short, path_len);
+#undef entry_size
+	}
+}
+
 static size_t read_entry(
 	git_index_entry **out,
 	git_index *index,
 	const void *buffer,
 	size_t buffer_size,
-	const char **last)
+	const char *last)
 {
 	size_t path_length, entry_size;
 	const char *path_ptr;
@@ -2344,35 +2357,34 @@
 			path_length = path_end - path_ptr;
 		}
 
-		if (entry.flags & GIT_IDXENTRY_EXTENDED)
-			entry_size = long_entry_size(path_length);
-		else
-			entry_size = short_entry_size(path_length);
-
-		if (INDEX_FOOTER_SIZE + entry_size > buffer_size)
-			return 0;
-
+		entry_size = index_entry_size(path_length, 0, entry.flags);
 		entry.path = (char *)path_ptr;
 	} else {
 		size_t varint_len;
-		size_t shared = git_decode_varint((const unsigned char *)path_ptr, 
-						  &varint_len);
-		size_t len = strlen(path_ptr + varint_len);
-		size_t last_len = strlen(*last);
-		size_t tmp_path_len;
+		size_t strip_len = git_decode_varint((const unsigned char *)path_ptr,
+						     &varint_len);
+		size_t last_len = strlen(last);
+		size_t prefix_len = last_len - strip_len;
+		size_t suffix_len = strlen(path_ptr + varint_len);
+		size_t path_len;
 
 		if (varint_len == 0)
 			return index_error_invalid("incorrect prefix length");
 
-		GITERR_CHECK_ALLOC_ADD(&tmp_path_len, shared, len + 1);
-		tmp_path = git__malloc(tmp_path_len);
+		GITERR_CHECK_ALLOC_ADD(&path_len, prefix_len, suffix_len);
+		GITERR_CHECK_ALLOC_ADD(&path_len, path_len, 1);
+		tmp_path = git__malloc(path_len);
 		GITERR_CHECK_ALLOC(tmp_path);
-		memcpy(tmp_path, last, last_len);
-		memcpy(tmp_path + last_len, path_ptr + varint_len, len);
-		entry_size = long_entry_size(shared + len);
+
+		memcpy(tmp_path, last, prefix_len);
+		memcpy(tmp_path + prefix_len, path_ptr + varint_len, suffix_len + 1);
+		entry_size = index_entry_size(suffix_len, varint_len, entry.flags);
 		entry.path = tmp_path;
 	}
 
+	if (INDEX_FOOTER_SIZE + entry_size > buffer_size)
+		return 0;
+
 	if (index_entry_dup(out, index, &entry) < 0) {
 		git__free(tmp_path);
 		return 0;
@@ -2445,7 +2457,7 @@
 	unsigned int i;
 	struct index_header header = { 0 };
 	git_oid checksum_calculated, checksum_expected;
-	const char **last = NULL;
+	const char *last = NULL;
 	const char *empty = "";
 
 #define seek_forward(_increase) { \
@@ -2469,7 +2481,7 @@
 
 	index->version = header.version;
 	if (index->version >= INDEX_VERSION_NUMBER_COMP)
-		last = &empty;
+		last = empty;
 
 	seek_forward(INDEX_HEADER_SIZE);
 
@@ -2504,6 +2516,9 @@
 		}
 		error = 0;
 
+		if (index->version >= INDEX_VERSION_NUMBER_COMP)
+			last = entry->path;
+
 		seek_forward(entry_size);
 	}
 
@@ -2574,11 +2589,12 @@
 	return (extended > 0);
 }
 
-static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char **last)
+static int write_disk_entry(git_filebuf *file, git_index_entry *entry, const char *last)
 {
 	void *mem = NULL;
 	struct entry_short *ondisk;
 	size_t path_len, disk_size;
+	int varint_len = 0;
 	char *path;
 	const char *path_start = entry->path;
 	size_t same_len = 0;
@@ -2586,7 +2602,7 @@
 	path_len = ((struct entry_internal *)entry)->pathlen;
 
 	if (last) {
-		const char *last_c = *last;
+		const char *last_c = last;
 
 		while (*path_start == *last_c) {
 			if (!*path_start || !*last_c)
@@ -2596,13 +2612,10 @@
 			++same_len;
 		}
 		path_len -= same_len;
-		*last = entry->path;
+		varint_len = git_encode_varint(NULL, 0, same_len);
 	}
 
-	if (entry->flags & GIT_IDXENTRY_EXTENDED)
-		disk_size = long_entry_size(path_len);
-	else
-		disk_size = short_entry_size(path_len);
+	disk_size = index_entry_size(path_len, varint_len, entry->flags);
 
 	if (git_filebuf_reserve(file, &mem, disk_size) < 0)
 		return -1;
@@ -2642,16 +2655,34 @@
 		ondisk_ext->flags_extended = htons(entry->flags_extended &
 			GIT_IDXENTRY_EXTENDED_FLAGS);
 		path = ondisk_ext->path;
-	}
-	else
+		disk_size -= offsetof(struct entry_long, path);
+	} else {
 		path = ondisk->path;
+		disk_size -= offsetof(struct entry_short, path);
+	}
 
 	if (last) {
-		path += git_encode_varint((unsigned char *) path,
-					  disk_size,
-					  path_len - same_len);
+		varint_len = git_encode_varint((unsigned char *) path,
+					  disk_size, same_len);
+		assert(varint_len > 0);
+		path += varint_len;
+		disk_size -= varint_len;
+
+		/*
+		 * If using path compression, we are not allowed
+		 * to have additional trailing NULs.
+		 */
+		assert(disk_size == path_len + 1);
+	} else {
+		/*
+		 * If no path compression is used, we do have
+		 * NULs as padding. As such, simply assert that
+		 * we have enough space left to write the path.
+		 */
+		assert(disk_size > path_len);
 	}
-	memcpy(path, path_start, path_len);
+
+	memcpy(path, path_start, path_len + 1);
 
 	return 0;
 }
@@ -2662,8 +2693,7 @@
 	size_t i;
 	git_vector case_sorted, *entries;
 	git_index_entry *entry;
-	const char **last = NULL;
-	const char *empty = "";
+	const char *last = NULL;
 
 	/* If index->entries is sorted case-insensitively, then we need
 	 * to re-sort it case-sensitively before writing */
@@ -2676,11 +2706,14 @@
 	}
 
 	if (index->version >= INDEX_VERSION_NUMBER_COMP)
-		last = &empty;
+		last = "";
 
-	git_vector_foreach(entries, i, entry)
+	git_vector_foreach(entries, i, entry) {
 		if ((error = write_disk_entry(file, entry, last)) < 0)
 			break;
+		if (index->version >= INDEX_VERSION_NUMBER_COMP)
+			last = entry->path;
+	}
 
 	if (index->ignore_case)
 		git_vector_free(&case_sorted);
diff --git a/src/openssl_stream.c b/src/openssl_stream.c
index 841dcce..759c501 100644
--- a/src/openssl_stream.c
+++ b/src/openssl_stream.c
@@ -103,7 +103,7 @@
 	ssl_opts |= SSL_OP_NO_COMPRESSION;
 #endif
 
-#if OPENSSL_VERSION_NUMBER < 0x10100000L
+#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER)
 	SSL_load_error_strings();
 	OpenSSL_add_ssl_algorithms();
 #else
diff --git a/src/path.c b/src/path.c
index b7205a6..5fc7a05 100644
--- a/src/path.c
+++ b/src/path.c
@@ -1708,6 +1708,7 @@
 	unsigned int flags)
 {
 	int protectHFS = 0, protectNTFS = 0;
+	int error = 0;
 
 	flags |= GIT_PATH_REJECT_DOT_GIT_LITERAL;
 
@@ -1720,13 +1721,13 @@
 #endif
 
 	if (repo && !protectHFS)
-		git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS);
-	if (protectHFS)
+		error = git_repository__cvar(&protectHFS, repo, GIT_CVAR_PROTECTHFS);
+	if (!error && protectHFS)
 		flags |= GIT_PATH_REJECT_DOT_GIT_HFS;
 
 	if (repo && !protectNTFS)
-		git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS);
-	if (protectNTFS)
+		error = git_repository__cvar(&protectNTFS, repo, GIT_CVAR_PROTECTNTFS);
+	if (!error && protectNTFS)
 		flags |= GIT_PATH_REJECT_DOT_GIT_NTFS;
 
 	return flags;
diff --git a/src/refdb_fs.c b/src/refdb_fs.c
index b325d27..988a14b 100644
--- a/src/refdb_fs.c
+++ b/src/refdb_fs.c
@@ -1140,7 +1140,7 @@
 static int maybe_append_head(refdb_fs_backend *backend, const git_reference *ref, const git_signature *who, const char *message)
 {
 	int error;
-	git_oid old_id = {{0}};
+	git_oid old_id;
 	git_reference *tmp = NULL, *head = NULL, *peeled = NULL;
 	const char *name;
 
@@ -1148,7 +1148,8 @@
 		return 0;
 
 	/* if we can't resolve, we use {0}*40 as old id */
-	git_reference_name_to_id(&old_id, backend->repo, ref->name);
+	if (git_reference_name_to_id(&old_id, backend->repo, ref->name) < 0)
+		memset(&old_id, 0, sizeof(old_id));
 
 	if ((error = git_reference_lookup(&head, backend->repo, GIT_HEAD_FILE)) < 0)
 		return error;
diff --git a/src/refs.c b/src/refs.c
index 632a529..f7120d9 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -622,15 +622,25 @@
 static int update_wt_heads(git_repository *repo, const char *path, void *payload)
 {
 	rename_cb_data *data = (rename_cb_data *) payload;
-	git_reference *head;
+	git_reference *head = NULL;
 	char *gitdir = NULL;
-	int error = 0;
+	int error;
 
-	if (git_reference__read_head(&head, repo, path) < 0 ||
-	    git_reference_type(head) != GIT_REF_SYMBOLIC ||
-	    git__strcmp(head->target.symbolic, data->old_name) != 0 ||
-	    (gitdir = git_path_dirname(path)) == NULL)
+	if ((error = git_reference__read_head(&head, repo, path)) < 0) {
+		giterr_set(GITERR_REFERENCE, "could not read HEAD when renaming references");
 		goto out;
+	}
+
+	if ((gitdir = git_path_dirname(path)) == NULL) {
+		error = -1;
+		goto out;
+	}
+
+	if (git_reference_type(head) != GIT_REF_SYMBOLIC ||
+	    git__strcmp(head->target.symbolic, data->old_name) != 0) {
+		error = 0;
+		goto out;
+	}
 
 	/* Update HEAD it was pointing to the reference being renamed */
 	if ((error = git_repository_create_head(gitdir, data->new_name)) < 0) {
diff --git a/src/varint.c b/src/varint.c
index 2f86860..beac8c7 100644
--- a/src/varint.c
+++ b/src/varint.c
@@ -36,7 +36,7 @@
 	while (value >>= 7)
 		varint[--pos] = 128 | (--value & 127);
 	if (buf) {
-		if (bufsize < pos)
+		if (bufsize < (sizeof(varint) - pos))
 			return -1;
 		memcpy(buf, varint + pos, sizeof(varint) - pos);
 	}
diff --git a/src/worktree.c b/src/worktree.c
index f224b23..ede155b 100644
--- a/src/worktree.c
+++ b/src/worktree.c
@@ -212,7 +212,7 @@
 		goto out;
 
 out:
-	free(name);
+	git__free(name);
 	git_buf_free(&parent);
 
 	return error;
diff --git a/tests/core/encoding.c b/tests/core/encoding.c
index 7d91720..a677afe 100644
--- a/tests/core/encoding.c
+++ b/tests/core/encoding.c
@@ -29,6 +29,9 @@
 	cl_assert(git_encode_varint(buf, 100, 65) == 1);
 	cl_assert(buf[0] == 'A');
 
+	cl_assert(git_encode_varint(buf, 1, 1) == 1);
+	cl_assert(!memcmp(buf, "\x01", 1));
+
 	cl_assert(git_encode_varint(buf, 100, 267869656) == 4);
 	cl_assert(!memcmp(buf, "\xfe\xdc\xbaX", 4));
 
diff --git a/tests/index/version.c b/tests/index/version.c
index 3fd240d..7ada302 100644
--- a/tests/index/version.c
+++ b/tests/index/version.c
@@ -3,39 +3,135 @@
 
 static git_repository *g_repo = NULL;
 
+void test_index_version__cleanup(void)
+{
+        cl_git_sandbox_cleanup();
+        g_repo = NULL;
+}
+
+void test_index_version__can_read_v4(void)
+{
+	const char *paths[] = {
+	    "file.tx", "file.txt", "file.txz", "foo", "zzz",
+	};
+	git_index *index;
+	size_t i;
+
+	g_repo = cl_git_sandbox_init("indexv4");
+
+	cl_git_pass(git_repository_index(&index, g_repo));
+	cl_assert_equal_sz(git_index_entrycount(index), 5);
+
+	for (i = 0; i < ARRAY_SIZE(paths); i++) {
+		const git_index_entry *entry =
+		    git_index_get_bypath(index, paths[i], GIT_INDEX_STAGE_NORMAL);
+
+		cl_assert(entry != NULL);
+	}
+
+	git_index_free(index);
+}
+
 void test_index_version__can_write_v4(void)
 {
+	const char *paths[] = {
+	    "foo",
+	    "foox",
+	    "foobar",
+	    "foobal",
+	    "x",
+	    "xz",
+	    "xyzzyx"
+	};
+	git_index_entry entry;
 	git_index *index;
-	const git_index_entry *entry;
+	size_t i;
 
-	g_repo = cl_git_sandbox_init("filemodes");
+	g_repo = cl_git_sandbox_init("empty_standard_repo");
 	cl_git_pass(git_repository_index(&index, g_repo));
-
-	cl_assert(index->on_disk);
-	cl_assert(git_index_version(index) == 2);
-
-	cl_assert(git_index_entrycount(index) == 6);
-
 	cl_git_pass(git_index_set_version(index, 4));
 
+	for (i = 0; i < ARRAY_SIZE(paths); i++) {
+		memset(&entry, 0, sizeof(entry));
+		entry.path = paths[i];
+		entry.mode = GIT_FILEMODE_BLOB;
+		cl_git_pass(git_index_add_frombuffer(index, &entry, paths[i],
+						     strlen(paths[i]) + 1));
+	}
+	cl_assert_equal_sz(git_index_entrycount(index), ARRAY_SIZE(paths));
+
 	cl_git_pass(git_index_write(index));
 	git_index_free(index);
 
 	cl_git_pass(git_repository_index(&index, g_repo));
 	cl_assert(git_index_version(index) == 4);
 
-	entry = git_index_get_bypath(index, "exec_off", 0);
-	cl_assert(entry);
-	entry = git_index_get_bypath(index, "exec_off2on_staged", 0);
-	cl_assert(entry);
-	entry = git_index_get_bypath(index, "exec_on", 0);
-	cl_assert(entry);
+	for (i = 0; i < ARRAY_SIZE(paths); i++) {
+		const git_index_entry *e;
+
+		cl_assert(e = git_index_get_bypath(index, paths[i], 0));
+		cl_assert_equal_s(paths[i], e->path);
+	}
 
 	git_index_free(index);
 }
 
-void test_index_version__cleanup(void)
+void test_index_version__v4_uses_path_compression(void)
 {
-        cl_git_sandbox_cleanup();
-        g_repo = NULL;
+	git_index_entry entry;
+	git_index *index;
+	char path[250], buf[1];
+	struct stat st;
+	char i, j;
+
+	memset(path, 'a', sizeof(path));
+	memset(buf, 'a', sizeof(buf));
+
+	memset(&entry, 0, sizeof(entry));
+	entry.path = path;
+	entry.mode = GIT_FILEMODE_BLOB;
+
+	g_repo = cl_git_sandbox_init("indexv4");
+	cl_git_pass(git_repository_index(&index, g_repo));
+
+	/* write 676 paths of 250 bytes length */
+	for (i = 'a'; i <= 'z'; i++) {
+		for (j = 'a'; j < 'z'; j++) {
+			path[ARRAY_SIZE(path) - 3] = i;
+			path[ARRAY_SIZE(path) - 2] = j;
+			path[ARRAY_SIZE(path) - 1] = '\0';
+			cl_git_pass(git_index_add_frombuffer(index, &entry, buf, sizeof(buf)));
+		}
+	}
+
+	cl_git_pass(git_index_write(index));
+	cl_git_pass(p_stat(git_index_path(index), &st));
+
+	/*
+	 * Without path compression, the written paths would at
+	 * least take
+	 *
+	 *    (entries * pathlen) = len
+	 *    (676 * 250) = 169000
+	 *
+	 *  bytes. As index v4 uses suffix-compression and our
+	 *  written paths only differ in the last two entries,
+	 *  this number will be much smaller, e.g.
+	 *
+	 *    (1 * pathlen) + (675 * 2) = len
+	 *    676 + 1350 = 2026
+	 *
+	 *    bytes.
+	 *
+	 *    Note that the above calculations do not include
+	 *    additional metadata of the index, e.g. OIDs or
+	 *    index extensions. Including those we get an index
+	 *    of approx. 200kB without compression and 40kB with
+	 *    compression. As this is a lot smaller than without
+	 *    compression, we can verify that path compression is
+	 *    used.
+	 */
+	cl_assert_(st.st_size < 75000, "path compression not enabled");
+
+	git_index_free(index);
 }
diff --git a/tests/resources/indexv4/.gitted/HEAD b/tests/resources/indexv4/.gitted/HEAD
new file mode 100644
index 0000000..cb089cd
--- /dev/null
+++ b/tests/resources/indexv4/.gitted/HEAD
@@ -0,0 +1 @@
+ref: refs/heads/master
diff --git a/tests/resources/indexv4/.gitted/config b/tests/resources/indexv4/.gitted/config
new file mode 100644
index 0000000..515f483
--- /dev/null
+++ b/tests/resources/indexv4/.gitted/config
@@ -0,0 +1,5 @@
+[core]
+	repositoryformatversion = 0
+	filemode = true
+	bare = false
+	logallrefupdates = true
diff --git a/tests/resources/indexv4/.gitted/index b/tests/resources/indexv4/.gitted/index
new file mode 100644
index 0000000..e8fc617
--- /dev/null
+++ b/tests/resources/indexv4/.gitted/index
Binary files differ
diff --git a/tests/resources/indexv4/.gitted/objects/4c/9109b3e671d851eec87e0e72f6305b582e7e99 b/tests/resources/indexv4/.gitted/objects/4c/9109b3e671d851eec87e0e72f6305b582e7e99
new file mode 100644
index 0000000..cedd594
--- /dev/null
+++ b/tests/resources/indexv4/.gitted/objects/4c/9109b3e671d851eec87e0e72f6305b582e7e99
Binary files differ
diff --git a/tests/resources/indexv4/.gitted/objects/b0/952dbb50bed5f01e03e31b296184cb183e54a7 b/tests/resources/indexv4/.gitted/objects/b0/952dbb50bed5f01e03e31b296184cb183e54a7
new file mode 100644
index 0000000..0ddc1d1
--- /dev/null
+++ b/tests/resources/indexv4/.gitted/objects/b0/952dbb50bed5f01e03e31b296184cb183e54a7
Binary files differ
diff --git a/tests/resources/indexv4/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/tests/resources/indexv4/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
new file mode 100644
index 0000000..7112238
--- /dev/null
+++ b/tests/resources/indexv4/.gitted/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files differ
diff --git a/tests/resources/indexv4/.gitted/refs/heads/master b/tests/resources/indexv4/.gitted/refs/heads/master
new file mode 100644
index 0000000..f3e960e
--- /dev/null
+++ b/tests/resources/indexv4/.gitted/refs/heads/master
@@ -0,0 +1 @@
+b0952dbb50bed5f01e03e31b296184cb183e54a7
diff --git a/tests/resources/indexv4/file.tx b/tests/resources/indexv4/file.tx
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/resources/indexv4/file.tx
diff --git a/tests/resources/indexv4/file.txt b/tests/resources/indexv4/file.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/resources/indexv4/file.txt
diff --git a/tests/resources/indexv4/file.txz b/tests/resources/indexv4/file.txz
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/resources/indexv4/file.txz
diff --git a/tests/resources/indexv4/foo b/tests/resources/indexv4/foo
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/resources/indexv4/foo
diff --git a/tests/resources/indexv4/zzz b/tests/resources/indexv4/zzz
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/resources/indexv4/zzz