Merge branch 'np/pack-safer'
* np/pack-safer: t5303: fix printf format string for portability t5303: work around printf breakage in dash pack-objects: don't leak pack window reference when splitting packs extend test coverage for latest pack corruption resilience improvements pack-objects: allow "fixing" a corrupted pack without a full repack make find_pack_revindex() aware of the nasty world make check_object() resilient to pack corruptions make packed_object_info() resilient to pack corruptions make unpack_object_header() non fatal better validation on delta base object offsets close another possibility for propagating pack corruptionmaint
						commit
						7b51b77dbc
					
				|  | @ -286,6 +286,7 @@ static unsigned long write_object(struct sha1file *f, | ||||||
| 				 */ | 				 */ | ||||||
|  |  | ||||||
| 	if (!to_reuse) { | 	if (!to_reuse) { | ||||||
|  | 		no_reuse: | ||||||
| 		if (!usable_delta) { | 		if (!usable_delta) { | ||||||
| 			buf = read_sha1_file(entry->idx.sha1, &type, &size); | 			buf = read_sha1_file(entry->idx.sha1, &type, &size); | ||||||
| 			if (!buf) | 			if (!buf) | ||||||
|  | @ -367,46 +368,60 @@ static unsigned long write_object(struct sha1file *f, | ||||||
| 		struct revindex_entry *revidx; | 		struct revindex_entry *revidx; | ||||||
| 		off_t offset; | 		off_t offset; | ||||||
|  |  | ||||||
| 		if (entry->delta) { | 		if (entry->delta) | ||||||
| 			type = (allow_ofs_delta && entry->delta->idx.offset) ? | 			type = (allow_ofs_delta && entry->delta->idx.offset) ? | ||||||
| 				OBJ_OFS_DELTA : OBJ_REF_DELTA; | 				OBJ_OFS_DELTA : OBJ_REF_DELTA; | ||||||
| 			reused_delta++; |  | ||||||
| 		} |  | ||||||
| 		hdrlen = encode_header(type, entry->size, header); | 		hdrlen = encode_header(type, entry->size, header); | ||||||
|  |  | ||||||
| 		offset = entry->in_pack_offset; | 		offset = entry->in_pack_offset; | ||||||
| 		revidx = find_pack_revindex(p, offset); | 		revidx = find_pack_revindex(p, offset); | ||||||
| 		datalen = revidx[1].offset - offset; | 		datalen = revidx[1].offset - offset; | ||||||
| 		if (!pack_to_stdout && p->index_version > 1 && | 		if (!pack_to_stdout && p->index_version > 1 && | ||||||
| 		    check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) | 		    check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) { | ||||||
| 			die("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1)); | 			error("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1)); | ||||||
|  | 			unuse_pack(&w_curs); | ||||||
|  | 			goto no_reuse; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		offset += entry->in_pack_header_size; | 		offset += entry->in_pack_header_size; | ||||||
| 		datalen -= entry->in_pack_header_size; | 		datalen -= entry->in_pack_header_size; | ||||||
|  | 		if (!pack_to_stdout && p->index_version == 1 && | ||||||
|  | 		    check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) { | ||||||
|  | 			error("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1)); | ||||||
|  | 			unuse_pack(&w_curs); | ||||||
|  | 			goto no_reuse; | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		if (type == OBJ_OFS_DELTA) { | 		if (type == OBJ_OFS_DELTA) { | ||||||
| 			off_t ofs = entry->idx.offset - entry->delta->idx.offset; | 			off_t ofs = entry->idx.offset - entry->delta->idx.offset; | ||||||
| 			unsigned pos = sizeof(dheader) - 1; | 			unsigned pos = sizeof(dheader) - 1; | ||||||
| 			dheader[pos] = ofs & 127; | 			dheader[pos] = ofs & 127; | ||||||
| 			while (ofs >>= 7) | 			while (ofs >>= 7) | ||||||
| 				dheader[--pos] = 128 | (--ofs & 127); | 				dheader[--pos] = 128 | (--ofs & 127); | ||||||
| 			if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) | 			if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) { | ||||||
|  | 				unuse_pack(&w_curs); | ||||||
| 				return 0; | 				return 0; | ||||||
|  | 			} | ||||||
| 			sha1write(f, header, hdrlen); | 			sha1write(f, header, hdrlen); | ||||||
| 			sha1write(f, dheader + pos, sizeof(dheader) - pos); | 			sha1write(f, dheader + pos, sizeof(dheader) - pos); | ||||||
| 			hdrlen += sizeof(dheader) - pos; | 			hdrlen += sizeof(dheader) - pos; | ||||||
|  | 			reused_delta++; | ||||||
| 		} else if (type == OBJ_REF_DELTA) { | 		} else if (type == OBJ_REF_DELTA) { | ||||||
| 			if (limit && hdrlen + 20 + datalen + 20 >= limit) | 			if (limit && hdrlen + 20 + datalen + 20 >= limit) { | ||||||
|  | 				unuse_pack(&w_curs); | ||||||
| 				return 0; | 				return 0; | ||||||
|  | 			} | ||||||
| 			sha1write(f, header, hdrlen); | 			sha1write(f, header, hdrlen); | ||||||
| 			sha1write(f, entry->delta->idx.sha1, 20); | 			sha1write(f, entry->delta->idx.sha1, 20); | ||||||
| 			hdrlen += 20; | 			hdrlen += 20; | ||||||
|  | 			reused_delta++; | ||||||
| 		} else { | 		} else { | ||||||
| 			if (limit && hdrlen + datalen + 20 >= limit) | 			if (limit && hdrlen + datalen + 20 >= limit) { | ||||||
|  | 				unuse_pack(&w_curs); | ||||||
| 				return 0; | 				return 0; | ||||||
|  | 			} | ||||||
| 			sha1write(f, header, hdrlen); | 			sha1write(f, header, hdrlen); | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (!pack_to_stdout && p->index_version == 1 && |  | ||||||
| 		    check_pack_inflate(p, &w_curs, offset, datalen, entry->size)) |  | ||||||
| 			die("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1)); |  | ||||||
| 		copy_pack_data(f, p, &w_curs, offset, datalen); | 		copy_pack_data(f, p, &w_curs, offset, datalen); | ||||||
| 		unuse_pack(&w_curs); | 		unuse_pack(&w_curs); | ||||||
| 		reused++; | 		reused++; | ||||||
|  | @ -1016,9 +1031,11 @@ static void check_object(struct object_entry *entry) | ||||||
| 		 * We want in_pack_type even if we do not reuse delta | 		 * We want in_pack_type even if we do not reuse delta | ||||||
| 		 * since non-delta representations could still be reused. | 		 * since non-delta representations could still be reused. | ||||||
| 		 */ | 		 */ | ||||||
| 		used = unpack_object_header_gently(buf, avail, | 		used = unpack_object_header_buffer(buf, avail, | ||||||
| 						   &entry->in_pack_type, | 						   &entry->in_pack_type, | ||||||
| 						   &entry->size); | 						   &entry->size); | ||||||
|  | 		if (used == 0) | ||||||
|  | 			goto give_up; | ||||||
|  |  | ||||||
| 		/* | 		/* | ||||||
| 		 * Determine if this is a delta and if so whether we can | 		 * Determine if this is a delta and if so whether we can | ||||||
|  | @ -1030,6 +1047,8 @@ static void check_object(struct object_entry *entry) | ||||||
| 			/* Not a delta hence we've already got all we need. */ | 			/* Not a delta hence we've already got all we need. */ | ||||||
| 			entry->type = entry->in_pack_type; | 			entry->type = entry->in_pack_type; | ||||||
| 			entry->in_pack_header_size = used; | 			entry->in_pack_header_size = used; | ||||||
|  | 			if (entry->type < OBJ_COMMIT || entry->type > OBJ_BLOB) | ||||||
|  | 				goto give_up; | ||||||
| 			unuse_pack(&w_curs); | 			unuse_pack(&w_curs); | ||||||
| 			return; | 			return; | ||||||
| 		case OBJ_REF_DELTA: | 		case OBJ_REF_DELTA: | ||||||
|  | @ -1046,19 +1065,25 @@ static void check_object(struct object_entry *entry) | ||||||
| 			ofs = c & 127; | 			ofs = c & 127; | ||||||
| 			while (c & 128) { | 			while (c & 128) { | ||||||
| 				ofs += 1; | 				ofs += 1; | ||||||
| 				if (!ofs || MSB(ofs, 7)) | 				if (!ofs || MSB(ofs, 7)) { | ||||||
| 					die("delta base offset overflow in pack for %s", | 					error("delta base offset overflow in pack for %s", | ||||||
| 					      sha1_to_hex(entry->idx.sha1)); | 					      sha1_to_hex(entry->idx.sha1)); | ||||||
|  | 					goto give_up; | ||||||
|  | 				} | ||||||
| 				c = buf[used_0++]; | 				c = buf[used_0++]; | ||||||
| 				ofs = (ofs << 7) + (c & 127); | 				ofs = (ofs << 7) + (c & 127); | ||||||
| 			} | 			} | ||||||
| 			if (ofs >= entry->in_pack_offset) |  | ||||||
| 				die("delta base offset out of bound for %s", |  | ||||||
| 				    sha1_to_hex(entry->idx.sha1)); |  | ||||||
| 			ofs = entry->in_pack_offset - ofs; | 			ofs = entry->in_pack_offset - ofs; | ||||||
|  | 			if (ofs <= 0 || ofs >= entry->in_pack_offset) { | ||||||
|  | 				error("delta base offset out of bound for %s", | ||||||
|  | 				      sha1_to_hex(entry->idx.sha1)); | ||||||
|  | 				goto give_up; | ||||||
|  | 			} | ||||||
| 			if (reuse_delta && !entry->preferred_base) { | 			if (reuse_delta && !entry->preferred_base) { | ||||||
| 				struct revindex_entry *revidx; | 				struct revindex_entry *revidx; | ||||||
| 				revidx = find_pack_revindex(p, ofs); | 				revidx = find_pack_revindex(p, ofs); | ||||||
|  | 				if (!revidx) | ||||||
|  | 					goto give_up; | ||||||
| 				base_ref = nth_packed_object_sha1(p, revidx->nr); | 				base_ref = nth_packed_object_sha1(p, revidx->nr); | ||||||
| 			} | 			} | ||||||
| 			entry->in_pack_header_size = used + used_0; | 			entry->in_pack_header_size = used + used_0; | ||||||
|  | @ -1078,6 +1103,7 @@ static void check_object(struct object_entry *entry) | ||||||
| 			 */ | 			 */ | ||||||
| 			entry->type = entry->in_pack_type; | 			entry->type = entry->in_pack_type; | ||||||
| 			entry->delta = base_entry; | 			entry->delta = base_entry; | ||||||
|  | 			entry->delta_size = entry->size; | ||||||
| 			entry->delta_sibling = base_entry->delta_child; | 			entry->delta_sibling = base_entry->delta_child; | ||||||
| 			base_entry->delta_child = entry; | 			base_entry->delta_child = entry; | ||||||
| 			unuse_pack(&w_curs); | 			unuse_pack(&w_curs); | ||||||
|  | @ -1092,6 +1118,8 @@ static void check_object(struct object_entry *entry) | ||||||
| 			 */ | 			 */ | ||||||
| 			entry->size = get_size_from_delta(p, &w_curs, | 			entry->size = get_size_from_delta(p, &w_curs, | ||||||
| 					entry->in_pack_offset + entry->in_pack_header_size); | 					entry->in_pack_offset + entry->in_pack_header_size); | ||||||
|  | 			if (entry->size == 0) | ||||||
|  | 				goto give_up; | ||||||
| 			unuse_pack(&w_curs); | 			unuse_pack(&w_curs); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
|  | @ -1101,6 +1129,7 @@ static void check_object(struct object_entry *entry) | ||||||
| 		 * with sha1_object_info() to find about the object type | 		 * with sha1_object_info() to find about the object type | ||||||
| 		 * at this point... | 		 * at this point... | ||||||
| 		 */ | 		 */ | ||||||
|  | 		give_up: | ||||||
| 		unuse_pack(&w_curs); | 		unuse_pack(&w_curs); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -1712,6 +1741,16 @@ static void prepare_pack(int window, int depth) | ||||||
|  |  | ||||||
| 	get_object_details(); | 	get_object_details(); | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * If we're locally repacking then we need to be doubly careful | ||||||
|  | 	 * from now on in order to make sure no stealth corruption gets | ||||||
|  | 	 * propagated to the new pack.  Clients receiving streamed packs | ||||||
|  | 	 * should validate everything they get anyway so no need to incur | ||||||
|  | 	 * the additional cost here in that case. | ||||||
|  | 	 */ | ||||||
|  | 	if (!pack_to_stdout) | ||||||
|  | 		do_check_packed_object_crc = 1; | ||||||
|  |  | ||||||
| 	if (!nr_objects || !window || !depth) | 	if (!nr_objects || !window || !depth) | ||||||
| 		return; | 		return; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @ -370,6 +370,8 @@ static void unpack_delta_entry(enum object_type type, unsigned long delta_size, | ||||||
| 			base_offset = (base_offset << 7) + (c & 127); | 			base_offset = (base_offset << 7) + (c & 127); | ||||||
| 		} | 		} | ||||||
| 		base_offset = obj_list[nr].offset - base_offset; | 		base_offset = obj_list[nr].offset - base_offset; | ||||||
|  | 		if (base_offset <= 0 || base_offset >= obj_list[nr].offset) | ||||||
|  | 			die("offset value out of bound for delta base object"); | ||||||
|  |  | ||||||
| 		delta_data = get_data(delta_size); | 		delta_data = get_data(delta_size); | ||||||
| 		if (dry_run || !delta_data) { | 		if (dry_run || !delta_data) { | ||||||
|  |  | ||||||
							
								
								
									
										5
									
								
								cache.h
								
								
								
								
							
							
						
						
									
										5
									
								
								cache.h
								
								
								
								
							|  | @ -574,6 +574,9 @@ extern int force_object_loose(const unsigned char *sha1, time_t mtime); | ||||||
| /* just like read_sha1_file(), but non fatal in presence of bad objects */ | /* just like read_sha1_file(), but non fatal in presence of bad objects */ | ||||||
| extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); | extern void *read_object(const unsigned char *sha1, enum object_type *type, unsigned long *size); | ||||||
|  |  | ||||||
|  | /* global flag to enable extra checks when accessing packed objects */ | ||||||
|  | extern int do_check_packed_object_crc; | ||||||
|  |  | ||||||
| extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); | extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type); | ||||||
|  |  | ||||||
| extern int move_temp_to_file(const char *tmpfile, const char *filename); | extern int move_temp_to_file(const char *tmpfile, const char *filename); | ||||||
|  | @ -762,7 +765,7 @@ extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t | ||||||
| extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t); | extern off_t nth_packed_object_offset(const struct packed_git *, uint32_t); | ||||||
| extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); | extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); | ||||||
| extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *); | extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *); | ||||||
| extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); | extern unsigned long unpack_object_header_buffer(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); | ||||||
| extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); | extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); | ||||||
| extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *); | extern const char *packed_object_info_detail(struct packed_git *, off_t, unsigned long *, unsigned long *, unsigned int *, unsigned char *); | ||||||
| extern int matches_pack_name(struct packed_git *p, const char *name); | extern int matches_pack_name(struct packed_git *p, const char *name); | ||||||
|  |  | ||||||
|  | @ -338,7 +338,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_ | ||||||
| 			base_offset = (base_offset << 7) + (c & 127); | 			base_offset = (base_offset << 7) + (c & 127); | ||||||
| 		} | 		} | ||||||
| 		delta_base->offset = obj->idx.offset - base_offset; | 		delta_base->offset = obj->idx.offset - base_offset; | ||||||
| 		if (delta_base->offset >= obj->idx.offset) | 		if (delta_base->offset <= 0 || delta_base->offset >= obj->idx.offset) | ||||||
| 			bad_object(obj->idx.offset, "delta base offset is out of bound"); | 			bad_object(obj->idx.offset, "delta base offset is out of bound"); | ||||||
| 		break; | 		break; | ||||||
| 	case OBJ_COMMIT: | 	case OBJ_COMMIT: | ||||||
|  |  | ||||||
|  | @ -140,7 +140,8 @@ struct revindex_entry *find_pack_revindex(struct packed_git *p, off_t ofs) | ||||||
| 		else | 		else | ||||||
| 			lo = mi + 1; | 			lo = mi + 1; | ||||||
| 	} while (lo < hi); | 	} while (lo < hi); | ||||||
| 	die("internal error: pack revindex corrupt"); | 	error("bad offset for revindex"); | ||||||
|  | 	return NULL; | ||||||
| } | } | ||||||
|  |  | ||||||
| void discard_revindex(void) | void discard_revindex(void) | ||||||
|  |  | ||||||
							
								
								
									
										83
									
								
								sha1_file.c
								
								
								
								
							
							
						
						
									
										83
									
								
								sha1_file.c
								
								
								
								
							|  | @ -1122,7 +1122,8 @@ static int legacy_loose_object(unsigned char *map) | ||||||
| 		return 0; | 		return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep) | unsigned long unpack_object_header_buffer(const unsigned char *buf, | ||||||
|  | 		unsigned long len, enum object_type *type, unsigned long *sizep) | ||||||
| { | { | ||||||
| 	unsigned shift; | 	unsigned shift; | ||||||
| 	unsigned char c; | 	unsigned char c; | ||||||
|  | @ -1134,10 +1135,10 @@ unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned lon | ||||||
| 	size = c & 15; | 	size = c & 15; | ||||||
| 	shift = 4; | 	shift = 4; | ||||||
| 	while (c & 0x80) { | 	while (c & 0x80) { | ||||||
| 		if (len <= used) | 		if (len <= used || sizeof(long) * 8 <= shift) { | ||||||
| 			return 0; | 			error("bad object header"); | ||||||
| 		if (sizeof(long) * 8 <= shift) |  | ||||||
| 			return 0; | 			return 0; | ||||||
|  | 		} | ||||||
| 		c = buf[used++]; | 		c = buf[used++]; | ||||||
| 		size += (c & 0x7f) << shift; | 		size += (c & 0x7f) << shift; | ||||||
| 		shift += 7; | 		shift += 7; | ||||||
|  | @ -1176,7 +1177,7 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon | ||||||
| 	 * really worth it and we don't write it any longer.  But we | 	 * really worth it and we don't write it any longer.  But we | ||||||
| 	 * can still read it. | 	 * can still read it. | ||||||
| 	 */ | 	 */ | ||||||
| 	used = unpack_object_header_gently(map, mapsize, &type, &size); | 	used = unpack_object_header_buffer(map, mapsize, &type, &size); | ||||||
| 	if (!used || !valid_loose_object_type[type]) | 	if (!used || !valid_loose_object_type[type]) | ||||||
| 		return -1; | 		return -1; | ||||||
| 	map += used; | 	map += used; | ||||||
|  | @ -1325,8 +1326,10 @@ unsigned long get_size_from_delta(struct packed_git *p, | ||||||
| 	} while ((st == Z_OK || st == Z_BUF_ERROR) && | 	} while ((st == Z_OK || st == Z_BUF_ERROR) && | ||||||
| 		 stream.total_out < sizeof(delta_head)); | 		 stream.total_out < sizeof(delta_head)); | ||||||
| 	inflateEnd(&stream); | 	inflateEnd(&stream); | ||||||
| 	if ((st != Z_STREAM_END) && stream.total_out != sizeof(delta_head)) | 	if ((st != Z_STREAM_END) && stream.total_out != sizeof(delta_head)) { | ||||||
| 		die("delta data unpack-initial failed"); | 		error("delta data unpack-initial failed"); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	/* Examine the initial part of the delta to figure out | 	/* Examine the initial part of the delta to figure out | ||||||
| 	 * the result size. | 	 * the result size. | ||||||
|  | @ -1367,7 +1370,7 @@ static off_t get_delta_base(struct packed_git *p, | ||||||
| 			base_offset = (base_offset << 7) + (c & 127); | 			base_offset = (base_offset << 7) + (c & 127); | ||||||
| 		} | 		} | ||||||
| 		base_offset = delta_obj_offset - base_offset; | 		base_offset = delta_obj_offset - base_offset; | ||||||
| 		if (base_offset >= delta_obj_offset) | 		if (base_offset <= 0 || base_offset >= delta_obj_offset) | ||||||
| 			return 0;  /* out of bound */ | 			return 0;  /* out of bound */ | ||||||
| 		*curpos += used; | 		*curpos += used; | ||||||
| 	} else if (type == OBJ_REF_DELTA) { | 	} else if (type == OBJ_REF_DELTA) { | ||||||
|  | @ -1393,15 +1396,32 @@ static int packed_delta_info(struct packed_git *p, | ||||||
| 	off_t base_offset; | 	off_t base_offset; | ||||||
|  |  | ||||||
| 	base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset); | 	base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset); | ||||||
|  | 	if (!base_offset) | ||||||
|  | 		return OBJ_BAD; | ||||||
| 	type = packed_object_info(p, base_offset, NULL); | 	type = packed_object_info(p, base_offset, NULL); | ||||||
|  | 	if (type <= OBJ_NONE) { | ||||||
|  | 		struct revindex_entry *revidx; | ||||||
|  | 		const unsigned char *base_sha1; | ||||||
|  | 		revidx = find_pack_revindex(p, base_offset); | ||||||
|  | 		if (!revidx) | ||||||
|  | 			return OBJ_BAD; | ||||||
|  | 		base_sha1 = nth_packed_object_sha1(p, revidx->nr); | ||||||
|  | 		mark_bad_packed_object(p, base_sha1); | ||||||
|  | 		type = sha1_object_info(base_sha1, NULL); | ||||||
|  | 		if (type <= OBJ_NONE) | ||||||
|  | 			return OBJ_BAD; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	/* We choose to only get the type of the base object and | 	/* We choose to only get the type of the base object and | ||||||
| 	 * ignore potentially corrupt pack file that expects the delta | 	 * ignore potentially corrupt pack file that expects the delta | ||||||
| 	 * based on a base with a wrong size.  This saves tons of | 	 * based on a base with a wrong size.  This saves tons of | ||||||
| 	 * inflate() calls. | 	 * inflate() calls. | ||||||
| 	 */ | 	 */ | ||||||
| 	if (sizep) | 	if (sizep) { | ||||||
| 		*sizep = get_size_from_delta(p, w_curs, curpos); | 		*sizep = get_size_from_delta(p, w_curs, curpos); | ||||||
|  | 		if (*sizep == 0) | ||||||
|  | 			type = OBJ_BAD; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return type; | 	return type; | ||||||
| } | } | ||||||
|  | @ -1423,9 +1443,10 @@ static int unpack_object_header(struct packed_git *p, | ||||||
| 	 * insane, so we know won't exceed what we have been given. | 	 * insane, so we know won't exceed what we have been given. | ||||||
| 	 */ | 	 */ | ||||||
| 	base = use_pack(p, w_curs, *curpos, &left); | 	base = use_pack(p, w_curs, *curpos, &left); | ||||||
| 	used = unpack_object_header_gently(base, left, &type, sizep); | 	used = unpack_object_header_buffer(base, left, &type, sizep); | ||||||
| 	if (!used) | 	if (!used) { | ||||||
| 		die("object offset outside of pack file"); | 		type = OBJ_BAD; | ||||||
|  | 	} else | ||||||
| 		*curpos += used; | 		*curpos += used; | ||||||
|  |  | ||||||
| 	return type; | 	return type; | ||||||
|  | @ -1510,8 +1531,9 @@ static int packed_object_info(struct packed_git *p, off_t obj_offset, | ||||||
| 			*sizep = size; | 			*sizep = size; | ||||||
| 		break; | 		break; | ||||||
| 	default: | 	default: | ||||||
| 		die("pack %s contains unknown object type %d", | 		error("unknown object type %i at offset %"PRIuMAX" in %s", | ||||||
| 		    p->pack_name, type); | 		      type, (uintmax_t)obj_offset, p->pack_name); | ||||||
|  | 		type = OBJ_BAD; | ||||||
| 	} | 	} | ||||||
| 	unuse_pack(&w_curs); | 	unuse_pack(&w_curs); | ||||||
| 	return type; | 	return type; | ||||||
|  | @ -1675,9 +1697,12 @@ static void *unpack_delta_entry(struct packed_git *p, | ||||||
| 		 * This is costly but should happen only in the presence | 		 * This is costly but should happen only in the presence | ||||||
| 		 * of a corrupted pack, and is better than failing outright. | 		 * of a corrupted pack, and is better than failing outright. | ||||||
| 		 */ | 		 */ | ||||||
| 		struct revindex_entry *revidx = find_pack_revindex(p, base_offset); | 		struct revindex_entry *revidx; | ||||||
| 		const unsigned char *base_sha1 = | 		const unsigned char *base_sha1; | ||||||
| 					nth_packed_object_sha1(p, revidx->nr); | 		revidx = find_pack_revindex(p, base_offset); | ||||||
|  | 		if (!revidx) | ||||||
|  | 			return NULL; | ||||||
|  | 		base_sha1 = nth_packed_object_sha1(p, revidx->nr); | ||||||
| 		error("failed to read delta base object %s" | 		error("failed to read delta base object %s" | ||||||
| 		      " at offset %"PRIuMAX" from %s", | 		      " at offset %"PRIuMAX" from %s", | ||||||
| 		      sha1_to_hex(base_sha1), (uintmax_t)base_offset, | 		      sha1_to_hex(base_sha1), (uintmax_t)base_offset, | ||||||
|  | @ -1706,6 +1731,8 @@ static void *unpack_delta_entry(struct packed_git *p, | ||||||
| 	return result; | 	return result; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | int do_check_packed_object_crc; | ||||||
|  |  | ||||||
| void *unpack_entry(struct packed_git *p, off_t obj_offset, | void *unpack_entry(struct packed_git *p, off_t obj_offset, | ||||||
| 		   enum object_type *type, unsigned long *sizep) | 		   enum object_type *type, unsigned long *sizep) | ||||||
| { | { | ||||||
|  | @ -1713,6 +1740,19 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, | ||||||
| 	off_t curpos = obj_offset; | 	off_t curpos = obj_offset; | ||||||
| 	void *data; | 	void *data; | ||||||
|  |  | ||||||
|  | 	if (do_check_packed_object_crc && p->index_version > 1) { | ||||||
|  | 		struct revindex_entry *revidx = find_pack_revindex(p, obj_offset); | ||||||
|  | 		unsigned long len = revidx[1].offset - obj_offset; | ||||||
|  | 		if (check_pack_crc(p, &w_curs, obj_offset, len, revidx->nr)) { | ||||||
|  | 			const unsigned char *sha1 = | ||||||
|  | 				nth_packed_object_sha1(p, revidx->nr); | ||||||
|  | 			error("bad packed object CRC for %s", | ||||||
|  | 			      sha1_to_hex(sha1)); | ||||||
|  | 			mark_bad_packed_object(p, sha1); | ||||||
|  | 			return NULL; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	*type = unpack_object_header(p, &w_curs, &curpos, sizep); | 	*type = unpack_object_header(p, &w_curs, &curpos, sizep); | ||||||
| 	switch (*type) { | 	switch (*type) { | ||||||
| 	case OBJ_OFS_DELTA: | 	case OBJ_OFS_DELTA: | ||||||
|  | @ -1966,7 +2006,14 @@ int sha1_object_info(const unsigned char *sha1, unsigned long *sizep) | ||||||
| 		if (!find_pack_entry(sha1, &e, NULL)) | 		if (!find_pack_entry(sha1, &e, NULL)) | ||||||
| 			return status; | 			return status; | ||||||
| 	} | 	} | ||||||
| 	return packed_object_info(e.p, e.offset, sizep); |  | ||||||
|  | 	status = packed_object_info(e.p, e.offset, sizep); | ||||||
|  | 	if (status < 0) { | ||||||
|  | 		mark_bad_packed_object(e.p, sha1); | ||||||
|  | 		status = sha1_object_info(sha1, sizep); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return status; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void *read_packed_sha1(const unsigned char *sha1, | static void *read_packed_sha1(const unsigned char *sha1, | ||||||
|  |  | ||||||
|  | @ -196,7 +196,8 @@ test_expect_success \ | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success \ | ||||||
|     '[index v2] 5) pack-objects refuses to reuse corrupted data' \ |     '[index v2] 5) pack-objects refuses to reuse corrupted data' \ | ||||||
|     'test_must_fail git pack-objects test-5 <obj-list' |     'test_must_fail git pack-objects test-5 <obj-list && | ||||||
|  |      test_must_fail git pack-objects --no-reuse-object test-6 <obj-list' | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success \ | ||||||
|     '[index v2] 6) verify-pack detects CRC mismatch' \ |     '[index v2] 6) verify-pack detects CRC mismatch' \ | ||||||
|  |  | ||||||
|  | @ -41,11 +41,17 @@ create_new_pack() { | ||||||
|     git verify-pack -v ${pack}.pack |     git verify-pack -v ${pack}.pack | ||||||
| } | } | ||||||
|  |  | ||||||
|  | do_repack() { | ||||||
|  |     pack=`printf "$blob_1\n$blob_2\n$blob_3\n" | | ||||||
|  |           git pack-objects $@ .git/objects/pack/pack` && | ||||||
|  |     pack=".git/objects/pack/pack-${pack}" | ||||||
|  | } | ||||||
|  |  | ||||||
| do_corrupt_object() { | do_corrupt_object() { | ||||||
|     ofs=`git show-index < ${pack}.idx | grep $1 | cut -f1 -d" "` && |     ofs=`git show-index < ${pack}.idx | grep $1 | cut -f1 -d" "` && | ||||||
|     ofs=$(($ofs + $2)) && |     ofs=$(($ofs + $2)) && | ||||||
|     chmod +w ${pack}.pack && |     chmod +w ${pack}.pack && | ||||||
|     dd if=/dev/zero of=${pack}.pack count=1 bs=1 conv=notrunc seek=$ofs && |     dd of=${pack}.pack count=1 bs=1 conv=notrunc seek=$ofs && | ||||||
|     test_must_fail git verify-pack ${pack}.pack |     test_must_fail git verify-pack ${pack}.pack | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -60,7 +66,7 @@ test_expect_success \ | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success \ | ||||||
|     'create corruption in header of first object' \ |     'create corruption in header of first object' \ | ||||||
|     'do_corrupt_object $blob_1 0 && |     'do_corrupt_object $blob_1 0 < /dev/zero && | ||||||
|      test_must_fail git cat-file blob $blob_1 > /dev/null && |      test_must_fail git cat-file blob $blob_1 > /dev/null && | ||||||
|      test_must_fail git cat-file blob $blob_2 > /dev/null && |      test_must_fail git cat-file blob $blob_2 > /dev/null && | ||||||
|      test_must_fail git cat-file blob $blob_3 > /dev/null' |      test_must_fail git cat-file blob $blob_3 > /dev/null' | ||||||
|  | @ -119,7 +125,7 @@ test_expect_success \ | ||||||
|     'create corruption in header of first delta' \ |     'create corruption in header of first delta' \ | ||||||
|     'create_new_pack && |     'create_new_pack && | ||||||
|      git prune-packed && |      git prune-packed && | ||||||
|      do_corrupt_object $blob_2 0 && |      do_corrupt_object $blob_2 0 < /dev/zero && | ||||||
|      git cat-file blob $blob_1 > /dev/null && |      git cat-file blob $blob_1 > /dev/null && | ||||||
|      test_must_fail git cat-file blob $blob_2 > /dev/null && |      test_must_fail git cat-file blob $blob_2 > /dev/null && | ||||||
|      test_must_fail git cat-file blob $blob_3 > /dev/null' |      test_must_fail git cat-file blob $blob_3 > /dev/null' | ||||||
|  | @ -133,6 +139,15 @@ test_expect_success \ | ||||||
|      git cat-file blob $blob_2 > /dev/null && |      git cat-file blob $blob_2 > /dev/null && | ||||||
|      git cat-file blob $blob_3 > /dev/null' |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
|  | test_expect_success \ | ||||||
|  |     '... and then a repack "clears" the corruption' \ | ||||||
|  |     'do_repack && | ||||||
|  |      git prune-packed && | ||||||
|  |      git verify-pack ${pack}.pack && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success \ | ||||||
|     'create corruption in data of first delta' \ |     'create corruption in data of first delta' \ | ||||||
|     'create_new_pack && |     'create_new_pack && | ||||||
|  | @ -152,11 +167,20 @@ test_expect_success \ | ||||||
|      git cat-file blob $blob_2 > /dev/null && |      git cat-file blob $blob_2 > /dev/null && | ||||||
|      git cat-file blob $blob_3 > /dev/null' |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
|  | test_expect_success \ | ||||||
|  |     '... and then a repack "clears" the corruption' \ | ||||||
|  |     'do_repack && | ||||||
|  |      git prune-packed && | ||||||
|  |      git verify-pack ${pack}.pack && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success \ | ||||||
|     'corruption in delta base reference of first delta (OBJ_REF_DELTA)' \ |     'corruption in delta base reference of first delta (OBJ_REF_DELTA)' \ | ||||||
|     'create_new_pack && |     'create_new_pack && | ||||||
|      git prune-packed && |      git prune-packed && | ||||||
|      do_corrupt_object $blob_2 2 && |      do_corrupt_object $blob_2 2 < /dev/zero && | ||||||
|      git cat-file blob $blob_1 > /dev/null && |      git cat-file blob $blob_1 > /dev/null && | ||||||
|      test_must_fail git cat-file blob $blob_2 > /dev/null && |      test_must_fail git cat-file blob $blob_2 > /dev/null && | ||||||
|      test_must_fail git cat-file blob $blob_3 > /dev/null' |      test_must_fail git cat-file blob $blob_3 > /dev/null' | ||||||
|  | @ -171,17 +195,75 @@ test_expect_success \ | ||||||
|      git cat-file blob $blob_3 > /dev/null' |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success \ | ||||||
|     'corruption in delta base reference of first delta (OBJ_OFS_DELTA)' \ |     '... and then a repack "clears" the corruption' \ | ||||||
|  |     'do_repack && | ||||||
|  |      git prune-packed && | ||||||
|  |      git verify-pack ${pack}.pack && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
|  | test_expect_success \ | ||||||
|  |     'corruption #0 in delta base reference of first delta (OBJ_OFS_DELTA)' \ | ||||||
|     'create_new_pack --delta-base-offset && |     'create_new_pack --delta-base-offset && | ||||||
|      git prune-packed && |      git prune-packed && | ||||||
|      do_corrupt_object $blob_2 2 && |      do_corrupt_object $blob_2 2 < /dev/zero && | ||||||
|      git cat-file blob $blob_1 > /dev/null && |      git cat-file blob $blob_1 > /dev/null && | ||||||
|      test_must_fail git cat-file blob $blob_2 > /dev/null && |      test_must_fail git cat-file blob $blob_2 > /dev/null && | ||||||
|      test_must_fail git cat-file blob $blob_3 > /dev/null' |      test_must_fail git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
| test_expect_success \ | test_expect_success \ | ||||||
|     '... and a redundant pack allows for full recovery too' \ |     '... but having a loose copy allows for full recovery' \ | ||||||
|     'mv ${pack}.idx tmp && |     'mv ${pack}.idx tmp && | ||||||
|  |      git hash-object -t blob -w file_2 && | ||||||
|  |      mv tmp ${pack}.idx && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
|  | test_expect_success \ | ||||||
|  |     '... and then a repack "clears" the corruption' \ | ||||||
|  |     'do_repack --delta-base-offset && | ||||||
|  |      git prune-packed && | ||||||
|  |      git verify-pack ${pack}.pack && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
|  | test_expect_success \ | ||||||
|  |     'corruption #1 in delta base reference of first delta (OBJ_OFS_DELTA)' \ | ||||||
|  |     'create_new_pack --delta-base-offset && | ||||||
|  |      git prune-packed && | ||||||
|  |      printf "\001" | do_corrupt_object $blob_2 2 && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      test_must_fail git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      test_must_fail git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
|  | test_expect_success \ | ||||||
|  |     '... but having a loose copy allows for full recovery' \ | ||||||
|  |     'mv ${pack}.idx tmp && | ||||||
|  |      git hash-object -t blob -w file_2 && | ||||||
|  |      mv tmp ${pack}.idx && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
|  | test_expect_success \ | ||||||
|  |     '... and then a repack "clears" the corruption' \ | ||||||
|  |     'do_repack --delta-base-offset && | ||||||
|  |      git prune-packed && | ||||||
|  |      git verify-pack ${pack}.pack && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      git cat-file blob $blob_3 > /dev/null' | ||||||
|  |  | ||||||
|  | test_expect_success \ | ||||||
|  |     '... and a redundant pack allows for full recovery too' \ | ||||||
|  |     'do_corrupt_object $blob_2 2 < /dev/zero && | ||||||
|  |      git cat-file blob $blob_1 > /dev/null && | ||||||
|  |      test_must_fail git cat-file blob $blob_2 > /dev/null && | ||||||
|  |      test_must_fail git cat-file blob $blob_3 > /dev/null && | ||||||
|  |      mv ${pack}.idx tmp && | ||||||
|      git hash-object -t blob -w file_1 && |      git hash-object -t blob -w file_1 && | ||||||
|      git hash-object -t blob -w file_2 && |      git hash-object -t blob -w file_2 && | ||||||
|      printf "$blob_1\n$blob_2\n" | git pack-objects .git/objects/pack/pack && |      printf "$blob_1\n$blob_2\n" | git pack-objects .git/objects/pack/pack && | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano