xref: /openbsd/usr.bin/compress/zipopen.c (revision 3ab258a1)
1 /*	$OpenBSD: zipopen.c,v 1.1 2022/10/22 14:41:27 millert Exp $	*/
2 
3 /*
4  * Copyright (c) 2022 Todd C. Miller <Todd.Miller@sudo.ws>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <unistd.h>
24 #include <limits.h>
25 #include <zlib.h>
26 #include "compress.h"
27 
28 #define MINIMUM(a, b)	(((a) < (b)) ? (a) : (b))
29 
30 /* Signatures for zip file headers we use. */
31 #define ZIPMAG 0x4b50		/* first two bytes of the zip signature */
32 #define LOCREM 0x0403		/* remaining two bytes in zip signature */
33 #define LOCSIG 0x04034b50	/* local file header signature */
34 #define EXTSIG 0x08074b50	/* extended local header signature */
35 
36 /* Header sizes. */
37 #define LOCHDR 30		/* size of local header, including signature */
38 #define EXTHDR 16		/* size of extended local header, inc sig */
39 
40 /* General purpose flag bits. */
41 #define CRPFLG 1		/* flag bit for encrypted entry */
42 #define EXTFLG 8		/* flag bit for extended local header */
43 
44 /* Extra field definitions */
45 #define	EF_ZIP64	0x0001	/* zip64 support */
46 #define	EF_TIME		0x5455	/* mtime, atime, ctime in UTC ("UT") */
47 #define EF_IZUNIX	0x5855	/* UNIX extra field ID ("UX") */
48 
49 #define Z_STORED 0		/* Stored uncompressed in .zip */
50 
51 struct zip_state {
52 	z_stream z_stream;	/* libz stream */
53 	uint8_t  z_buf[Z_BUFSIZE]; /* I/O buffer */
54 	uint8_t	 z_eof;		/* set if end of input file */
55 	uint8_t	 z_zip64;	/* 64-bit file sizes */
56 	uint16_t z_method;	/* Z_DEFLATE or Z_STORED */
57 	uint16_t z_flags;	/* general purpose flags */
58 	int	 z_fd;		/* zip file descriptor */
59 	uint32_t z_time;	/* timestamp (mtime) */
60 	uint32_t z_crc;		/* crc32 of uncompressed data */
61 	uint32_t z_ocrc;	/* crc32 of uncompressed data (from header) */
62 	uint32_t z_hlen;	/* length of the zip header */
63 	uint64_t z_ulen;	/* uncompressed data length (from header) */
64 	uint64_t z_total_in;	/* # bytes in */
65 	uint64_t z_total_out;	/* # bytes out */
66 };
67 
68 static int
get_byte(struct zip_state * s)69 get_byte(struct zip_state *s)
70 {
71 	if (s->z_eof)
72 		return EOF;
73 
74 	if (s->z_stream.avail_in == 0) {
75 		ssize_t nread = read(s->z_fd, s->z_buf, Z_BUFSIZE);
76 		if (nread <= 0) {
77 			s->z_eof = 1;
78 			return EOF;
79 		}
80 		s->z_stream.avail_in = nread;
81 		s->z_stream.next_in = s->z_buf;
82 	}
83 	s->z_stream.avail_in--;
84 	return *s->z_stream.next_in++;
85 }
86 
87 static uint16_t
get_uint16(struct zip_state * s)88 get_uint16(struct zip_state *s)
89 {
90 	uint16_t x;
91 
92 	x  = ((uint16_t)(get_byte(s) & 0xff));
93 	x |= ((uint16_t)(get_byte(s) & 0xff))<<8;
94 	return x;
95 }
96 
97 static uint32_t
get_uint32(struct zip_state * s)98 get_uint32(struct zip_state *s)
99 {
100 	uint32_t x;
101 
102 	x  = ((uint32_t)(get_byte(s) & 0xff));
103 	x |= ((uint32_t)(get_byte(s) & 0xff))<<8;
104 	x |= ((uint32_t)(get_byte(s) & 0xff))<<16;
105 	x |= ((uint32_t)(get_byte(s) & 0xff))<<24;
106 	return x;
107 }
108 
109 static uint64_t
get_uint64(struct zip_state * s)110 get_uint64(struct zip_state *s)
111 {
112 	uint64_t x;
113 
114 	x  = ((uint64_t)(get_byte(s) & 0xff));
115 	x |= ((uint64_t)(get_byte(s) & 0xff))<<8;
116 	x |= ((uint64_t)(get_byte(s) & 0xff))<<16;
117 	x |= ((uint64_t)(get_byte(s) & 0xff))<<24;
118 	x |= ((uint64_t)(get_byte(s) & 0xff))<<32;
119 	x |= ((uint64_t)(get_byte(s) & 0xff))<<40;
120 	x |= ((uint64_t)(get_byte(s) & 0xff))<<48;
121 	x |= ((uint64_t)(get_byte(s) & 0xff))<<56;
122 	return x;
123 }
124 
125 static int
get_header(struct zip_state * s,char * name,int gotmagic)126 get_header(struct zip_state *s, char *name, int gotmagic)
127 {
128 	int c, got_mtime = 0;
129 	uint16_t namelen, extlen;
130 	uint32_t sig;
131 
132 	/* Check the zip local file header signature. */
133 	if (!gotmagic) {
134 		sig = get_uint32(s);
135 		if (sig != LOCSIG) {
136 			errno = EFTYPE;
137 			return -1;
138 		}
139 	} else {
140 		sig = get_uint16(s);
141 		if (sig != LOCREM) {
142 			errno = EFTYPE;
143 			return -1;
144 		}
145 	}
146 
147 	/* Read the local header fields. */
148 	get_uint16(s);			/* min version */
149 	s->z_flags = get_uint16(s);	/* general purpose flags */
150 	s->z_method = get_uint16(s);	/* compression method */
151 	get_uint32(s);			/* DOS format mtime */
152 	s->z_ocrc = get_uint32(s);	/* 32-bit CRC */
153 	get_uint32(s);			/* compressed size */
154 	s->z_ulen = get_uint32(s);	/* uncompressed size */
155 	namelen = get_uint16(s);	/* file name length */
156 	extlen = get_uint16(s);		/* length of extra fields */
157 	s->z_hlen = LOCHDR;
158 
159 	/* Encrypted files not supported. */
160 	if (s->z_flags & CRPFLG) {
161 		errno = EFTYPE;
162 		return -1;
163 	}
164 
165 	/* Supported compression methods are deflate and store. */
166 	if (s->z_method != Z_DEFLATED && s->z_method != Z_STORED) {
167 		errno = EFTYPE;
168 		return -1;
169 	}
170 
171 	/* Store the original file name if present. */
172 	if (namelen != 0 && name != NULL) {
173 		const char *ep = name + PATH_MAX - 1;
174 		for (; namelen > 0; namelen--) {
175 			if ((c = get_byte(s)) == EOF)
176 				break;
177 			s->z_hlen++;
178 			if (c == '\0')
179 				break;
180 			if (name < ep)
181 				*name++ = c;
182 		}
183 		*name = '\0';
184 	}
185 
186 	/* Parse extra fields, if any. */
187 	while (extlen >= 4) {
188 		uint16_t sig;
189 		int fieldlen;
190 
191 		sig = get_uint16(s);
192 		fieldlen = get_uint16(s);
193 		s->z_hlen += 4;
194 		extlen -= 4;
195 
196 		switch (sig) {
197 		case EF_ZIP64:
198 			/* 64-bit file sizes */
199 			s->z_zip64 = 1;
200 			if (fieldlen >= 8) {
201 				s->z_ulen = get_uint64(s);
202 				s->z_hlen += 8;
203 				extlen -= 8;
204 				fieldlen -= 8;
205 			}
206 			break;
207 		case EF_TIME:
208 			/* UTC timestamps */
209 			if ((c = get_byte(s)) == EOF)
210 				break;
211 			s->z_hlen++;
212 			extlen--;
213 			fieldlen--;
214 			if (c & 1) {
215 				got_mtime = 1;
216 				s->z_time = get_uint32(s);
217 				s->z_hlen += 4;
218 				extlen -= 4;
219 				fieldlen -= 4;
220 			}
221 			break;
222 		case EF_IZUNIX:
223 			/* We prefer EF_TIME if it is present. */
224 			if (got_mtime)
225 				break;
226 
227 			/* skip atime, store mtime. */
228 			(void)get_uint32(s);
229 			s->z_time = get_uint32(s);
230 			s->z_hlen += 8;
231 			extlen -= 8;
232 			fieldlen -= 8;
233 			break;
234 		default:
235 			break;
236 		}
237 
238 		/* Consume any unparsed bytes in the field. */
239 		for (; fieldlen > 0; fieldlen--) {
240 			if (get_byte(s) == EOF)
241 				break;
242 			s->z_hlen++;
243 			extlen--;
244 		}
245 	}
246 	for (; extlen > 0; extlen--) {
247 		if (get_byte(s) == EOF)
248 			break;
249 		s->z_hlen++;
250 	}
251 
252 	return 0;
253 }
254 
255 void *
zip_ropen(int fd,char * name,int gotmagic)256 zip_ropen(int fd, char *name, int gotmagic)
257 {
258 	struct zip_state *s;
259 
260 	if (fd < 0)
261 		return NULL;
262 
263 	if ((s = calloc(1, sizeof(*s))) == NULL)
264 		return NULL;
265 
266 	s->z_fd = fd;
267 	s->z_crc = crc32(0, NULL, 0);
268 
269 	/* Request a raw inflate, there is no zlib/gzip header present. */
270 	if (inflateInit2(&s->z_stream, -MAX_WBITS) != Z_OK) {
271 		free(s);
272 		return NULL;
273 	}
274 	s->z_stream.next_in = s->z_buf;
275 	s->z_stream.avail_out = sizeof(s->z_buf);
276 
277 	/* Read the zip header. */
278 	if (get_header(s, name, gotmagic) != 0) {
279 		zip_close(s, NULL, NULL, NULL);
280 		s = NULL;
281 	}
282 
283 	return s;
284 }
285 
286 static int
zip_store(struct zip_state * s)287 zip_store(struct zip_state *s)
288 {
289 	int error = Z_OK;
290 	uLong copy_len;
291 
292 	if ((int)s->z_stream.avail_in <= 0)
293 		return s->z_stream.avail_in == 0 ? Z_STREAM_END : Z_DATA_ERROR;
294 
295 	/* For stored files we rely on z_ulen being set. */
296 	copy_len = MINIMUM(s->z_stream.avail_out, s->z_stream.avail_in);
297 	if (copy_len >= s->z_ulen - s->z_total_out) {
298 		/* Don't copy past the end of the file. */
299 		copy_len = s->z_ulen - s->z_total_out;
300 		error = Z_STREAM_END;
301 	}
302 
303 	memcpy(s->z_stream.next_out, s->z_stream.next_in, copy_len);
304 	s->z_stream.next_out += copy_len;
305 	s->z_stream.avail_out -= copy_len;
306 	s->z_stream.next_in += copy_len;
307 	s->z_stream.avail_in -= copy_len;
308 	s->z_total_in += copy_len;
309 	s->z_total_out += copy_len;
310 
311 	return error;
312 }
313 
314 int
zip_read(void * cookie,char * buf,int len)315 zip_read(void *cookie, char *buf, int len)
316 {
317 	struct zip_state *s = cookie;
318 	Bytef *ubuf = buf;
319 	int error = Z_OK;
320 
321 	s->z_stream.next_out = ubuf;
322 	s->z_stream.avail_out = len;
323 
324 	while (error == Z_OK && !s->z_eof && s->z_stream.avail_out != 0) {
325 		if (s->z_stream.avail_in == 0) {
326 			ssize_t nread = read(s->z_fd, s->z_buf, Z_BUFSIZE);
327 			switch (nread) {
328 			case -1:
329 				goto bad;
330 			case 0:
331 				s->z_eof = 1;
332 				continue;
333 			default:
334 				s->z_stream.avail_in = nread;
335 				s->z_stream.next_in = s->z_buf;
336 			}
337 		}
338 
339 		if (s->z_method == Z_DEFLATED) {
340 			/*
341 			 * Prevent overflow of z_stream.total_{in,out}
342 			 * which may be 32-bit.
343 			 */
344 			uLong prev_total_in = s->z_stream.total_in;
345 			uLong prev_total_out = s->z_stream.total_out;
346 			error = inflate(&s->z_stream, Z_NO_FLUSH);
347 			s->z_total_in += s->z_stream.total_in - prev_total_in;
348 			s->z_total_out += s->z_stream.total_out - prev_total_out;
349 		} else {
350 			/* File stored uncompressed. */
351 			error = zip_store(s);
352 		}
353 	}
354 
355 	switch (error) {
356 	case Z_OK:
357 		s->z_crc = crc32(s->z_crc, ubuf,
358 		    (uInt)(s->z_stream.next_out - ubuf));
359 		break;
360 	case Z_STREAM_END:
361 		s->z_eof = 1;
362 
363 		/*
364 		 * Check CRC and original size.
365 		 * These may be found in the local header or, if
366 		 * EXTFLG is set, immediately following the file.
367 		 */
368 		s->z_crc = crc32(s->z_crc, ubuf,
369 		    (uInt)(s->z_stream.next_out - ubuf));
370 
371 		if (s->z_flags & EXTFLG) {
372 			/*
373 			 * Read data descriptor:
374 			 *  signature 0x08074b50: 4 bytes
375 			 *  CRC-32: 4 bytes
376 			 *  compressed size: 4 or 8 bytes
377 			 *  uncompressed size: 4 or 8 bytes
378 			 */
379 			get_uint32(s);
380 			s->z_ocrc = get_uint32(s);
381 			if (s->z_zip64) {
382 				get_uint64(s);
383 				s->z_ulen = get_uint64(s);
384 				s->z_hlen += 8;
385 			} else {
386 				get_uint32(s);
387 				s->z_ulen = get_uint32(s);
388 			}
389 			s->z_hlen += EXTHDR;
390 		}
391 		if (s->z_ulen != s->z_total_out) {
392 			errno = EIO;
393 			goto bad;
394 		}
395 		if (s->z_ocrc != s->z_crc) {
396 			errno = EINVAL;
397 			goto bad;
398 		}
399 		break;
400 	case Z_DATA_ERROR:
401 		errno = EINVAL;
402 		goto bad;
403 	case Z_BUF_ERROR:
404 		errno = EIO;
405 		goto bad;
406 	default:
407 		goto bad;
408 	}
409 
410 	return len - s->z_stream.avail_out;
411 bad:
412 	return -1;
413 }
414 
415 int
zip_close(void * cookie,struct z_info * info,const char * name,struct stat * sb)416 zip_close(void *cookie, struct z_info *info, const char *name, struct stat *sb)
417 {
418 	struct zip_state *s = cookie;
419 	int error = 0;
420 
421 	if (s == NULL) {
422 		errno = EINVAL;
423 		return -1;
424 	}
425 
426 	if (info != NULL) {
427 		info->mtime = s->z_time;
428 		info->crc = s->z_crc;
429 		info->hlen = s->z_hlen;
430 		info->total_in = s->z_total_in;
431 		info->total_out = s->z_total_out;
432 	}
433 
434 	if (s->z_stream.state != NULL) {
435 		/* inflateEnd() overwrites errno. */
436 		(void)inflateEnd(&s->z_stream);
437 	}
438 
439 	/*
440 	 * Check for the presence of additional files in the .zip.
441 	 * Do not remove the original if we cannot extract all the files.
442 	 */
443 	s->z_eof = 0;
444 	if (get_header(s, NULL, 0) == 0) {
445 		errno = EEXIST;
446 		error = -1;
447 	}
448 
449 	(void)close(s->z_fd);
450 
451 	free(s);
452 
453 	return error;
454 }
455