Coverage Report

Created: 2026-10-14 06:52

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libzip/lib/zip_source_file_stdio_named.c
Line
Count
Source
1
/*
2
  zip_source_file_stdio_named.c -- source for stdio file opened by name
3
  Copyright (C) 1999-2024 Dieter Baron and Thomas Klausner
4
5
  This file is part of libzip, a library to manipulate ZIP archives.
6
  The authors can be contacted at <info@libzip.org>
7
8
  Redistribution and use in source and binary forms, with or without
9
  modification, are permitted provided that the following conditions
10
  are met:
11
  1. Redistributions of source code must retain the above copyright
12
     notice, this list of conditions and the following disclaimer.
13
  2. Redistributions in binary form must reproduce the above copyright
14
     notice, this list of conditions and the following disclaimer in
15
     the documentation and/or other materials provided with the
16
     distribution.
17
  3. The names of the authors may not be used to endorse or promote
18
     products derived from this software without specific prior
19
     written permission.
20
21
  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
22
  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23
  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24
  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
25
  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
27
  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
29
  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30
  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
31
  IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
*/
33
34
#include "zipint.h"
35
36
#include "zip_source_file.h"
37
#include "zip_source_file_stdio.h"
38
39
#include <fcntl.h>
40
#include <stdlib.h>
41
#include <string.h>
42
#include <sys/stat.h>
43
#ifdef HAVE_UNISTD_H
44
#include <unistd.h>
45
#endif
46
47
#ifdef HAVE_CLONEFILE
48
#include <sys/attr.h>
49
#include <sys/clonefile.h>
50
#define CAN_CLONE
51
#endif
52
#ifdef HAVE_FICLONERANGE
53
#include <linux/fs.h>
54
#include <sys/ioctl.h>
55
#define CAN_CLONE
56
#endif
57
58
static bool copy_permissions(zip_source_file_context_t *ctx);
59
static int create_temp_file(zip_source_file_context_t *ctx, bool create_file);
60
61
static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx);
62
static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx);
63
#ifdef CAN_CLONE
64
static zip_int64_t _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uint64_t offset);
65
#endif
66
static bool _zip_stdio_op_open(zip_source_file_context_t *ctx);
67
static zip_int64_t _zip_stdio_op_remove(zip_source_file_context_t *ctx);
68
static void _zip_stdio_op_rollback_write(zip_source_file_context_t *ctx);
69
static char *_zip_stdio_op_strdup(zip_source_file_context_t *ctx, const char *string);
70
static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len);
71
static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable);
72
73
/* clang-format off */
74
static zip_source_file_operations_t ops_stdio_named = {
75
    _zip_stdio_op_close,
76
    _zip_stdio_op_commit_write,
77
    _zip_stdio_op_create_temp_output,
78
#ifdef CAN_CLONE
79
    _zip_stdio_op_create_temp_output_cloning,
80
#else
81
    NULL,
82
#endif
83
    _zip_stdio_op_open,
84
    _zip_stdio_op_read,
85
    _zip_stdio_op_remove,
86
    _zip_stdio_op_rollback_write,
87
    _zip_stdio_op_seek,
88
    _zip_stdio_op_stat,
89
    _zip_stdio_op_strdup,
90
    _zip_stdio_op_tell,
91
    _zip_stdio_op_write
92
};
93
/* clang-format on */
94
95
0
ZIP_EXTERN zip_source_t *zip_source_file(zip_t *za, const char *fname, zip_uint64_t start, zip_int64_t len) {
96
0
    if (za == NULL) {
97
0
        return NULL;
98
0
    }
99
100
0
    return zip_source_file_create(fname, start, len, &za->error);
101
0
}
102
103
104
8.78k
ZIP_EXTERN zip_source_t *zip_source_file_create(const char *fname, zip_uint64_t start, zip_int64_t length, zip_error_t *error) {
105
8.78k
    if (fname == NULL || length < ZIP_LENGTH_UNCHECKED) {
106
0
        zip_error_set(error, ZIP_ER_INVAL, 0);
107
0
        return NULL;
108
0
    }
109
110
8.78k
    return zip_source_file_common_new(fname, NULL, start, length, NULL, &ops_stdio_named, NULL, error);
111
8.78k
}
112
113
114
0
static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx) {
115
0
    bool ok = true;
116
117
    /* On some systems, a write clears setuid/setgid bits, so make sure fclose doesn't write after copying permissions. */
118
0
    if (fflush((FILE *)ctx->fout) < 0) {
119
0
        zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
120
0
        (void)fclose((FILE *)ctx->fout);
121
0
        (void)remove(ctx->tmpname);
122
0
        return -1;
123
0
    }
124
125
0
    if (!ctx->temp_output_created) {
126
0
        ok = copy_permissions(ctx);
127
0
    }
128
129
0
    if (fclose((FILE *)ctx->fout) < 0) {
130
0
        zip_error_set(&ctx->error, ZIP_ER_CLOSE, errno);
131
0
        (void)remove(ctx->tmpname);
132
0
        return -1;
133
0
    }
134
0
    if (!ok) {
135
0
        (void)remove(ctx->tmpname);
136
0
        return -1;
137
0
    }
138
0
    if (rename(ctx->tmpname, ctx->fname) < 0) {
139
0
        zip_error_set(&ctx->error, ZIP_ER_RENAME, errno);
140
0
        (void)remove(ctx->tmpname);
141
0
        return -1;
142
0
    }
143
144
0
    return 0;
145
0
}
146
147
148
0
static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx) {
149
0
    int fd = create_temp_file(ctx, true);
150
151
0
    if (fd < 0) {
152
0
        return -1;
153
0
    }
154
155
0
    if ((ctx->fout = fdopen(fd, "r+b")) == NULL) {
156
0
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
157
0
        close(fd);
158
0
        (void)remove(ctx->tmpname);
159
0
        free(ctx->tmpname);
160
0
        ctx->tmpname = NULL;
161
0
        return -1;
162
0
    }
163
164
0
    return 0;
165
0
}
166
167
#ifdef CAN_CLONE
168
0
static zip_int64_t _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uint64_t offset) {
169
0
    FILE *tfp;
170
171
0
    if (offset > ZIP_OFF_MAX) {
172
0
        zip_error_set(&ctx->error, ZIP_ER_SEEK, E2BIG);
173
0
        return -1;
174
0
    }
175
176
#ifdef HAVE_CLONEFILE
177
    /* clonefile insists on creating the file, so just create a name */
178
    if (create_temp_file(ctx, false) < 0) {
179
        return -1;
180
    }
181
182
    if (clonefile(ctx->fname, ctx->tmpname, 0) < 0) {
183
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
184
        free(ctx->tmpname);
185
        ctx->tmpname = NULL;
186
        return -1;
187
    }
188
    if ((tfp = _zip_fopen_close_on_exec(ctx->tmpname, true)) == NULL) {
189
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
190
        (void)remove(ctx->tmpname);
191
        free(ctx->tmpname);
192
        ctx->tmpname = NULL;
193
        return -1;
194
    }
195
#else
196
0
    {
197
0
        int fd;
198
0
        struct file_clone_range range;
199
0
        zip_os_stat_t st;
200
201
0
        if (zip_os_fstat(fileno(ctx->f), &st) < 0) {
202
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
203
0
            return -1;
204
0
        }
205
206
0
        if ((fd = create_temp_file(ctx, true)) < 0) {
207
0
            return -1;
208
0
        }
209
210
0
        range.src_fd = fileno(ctx->f);
211
0
        range.src_offset = 0;
212
0
        range.src_length = ((offset + st.st_blksize - 1) / st.st_blksize) * st.st_blksize;
213
0
        if (range.src_length > st.st_size) {
214
0
            range.src_length = 0;
215
0
        }
216
0
        range.dest_offset = 0;
217
0
        if (ioctl(fd, FICLONERANGE, &range) < 0) {
218
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
219
0
            (void)close(fd);
220
0
            (void)remove(ctx->tmpname);
221
0
            free(ctx->tmpname);
222
0
            ctx->tmpname = NULL;
223
0
            return -1;
224
0
        }
225
226
0
        if ((tfp = fdopen(fd, "r+b")) == NULL) {
227
0
            zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
228
0
            (void)close(fd);
229
0
            (void)remove(ctx->tmpname);
230
0
            free(ctx->tmpname);
231
0
            ctx->tmpname = NULL;
232
0
            return -1;
233
0
        }
234
0
    }
235
0
#endif
236
237
0
    if (ftruncate(fileno(tfp), (off_t)offset) < 0) {
238
0
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
239
0
        (void)fclose(tfp);
240
0
        (void)remove(ctx->tmpname);
241
0
        free(ctx->tmpname);
242
0
        ctx->tmpname = NULL;
243
0
        return -1;
244
0
    }
245
0
    if (zip_os_fseek(tfp, (zip_off_t)offset, SEEK_SET) < 0) {
246
0
        zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
247
0
        (void)fclose(tfp);
248
0
        (void)remove(ctx->tmpname);
249
0
        free(ctx->tmpname);
250
0
        ctx->tmpname = NULL;
251
0
        return -1;
252
0
    }
253
254
0
    ctx->fout = tfp;
255
256
0
    return 0;
257
0
}
258
#endif
259
260
8.78k
static bool _zip_stdio_op_open(zip_source_file_context_t *ctx) {
261
8.78k
    if ((ctx->f = _zip_fopen_close_on_exec(ctx->fname, false)) == NULL) {
262
0
        zip_error_set(&ctx->error, ZIP_ER_OPEN, errno);
263
0
        return false;
264
0
    }
265
8.78k
    return true;
266
8.78k
}
267
268
269
0
static zip_int64_t _zip_stdio_op_remove(zip_source_file_context_t *ctx) {
270
0
    if (remove(ctx->fname) < 0) {
271
0
        zip_error_set(&ctx->error, ZIP_ER_REMOVE, errno);
272
0
        return -1;
273
0
    }
274
0
    return 0;
275
0
}
276
277
278
0
static void _zip_stdio_op_rollback_write(zip_source_file_context_t *ctx) {
279
0
    if (ctx->fout) {
280
0
        fclose((FILE *)ctx->fout);
281
0
    }
282
0
    (void)remove(ctx->tmpname);
283
0
}
284
285
8.78k
static char *_zip_stdio_op_strdup(zip_source_file_context_t *ctx, const char *string) {
286
8.78k
    return strdup(string);
287
8.78k
}
288
289
290
0
static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len) {
291
0
    size_t ret;
292
293
0
    clearerr((FILE *)ctx->fout);
294
0
    ret = fwrite(data, 1, len, (FILE *)ctx->fout);
295
0
    if (ret != len || ferror((FILE *)ctx->fout)) {
296
0
        zip_error_set(&ctx->error, ZIP_ER_WRITE, errno);
297
0
        return -1;
298
0
    }
299
300
0
    return (zip_int64_t)ret;
301
0
}
302
303
304
0
static int create_temp_file(zip_source_file_context_t *ctx, bool create_file) {
305
0
    char *temp;
306
0
    mode_t mode;
307
0
    zip_os_stat_t st;
308
0
    int fd = 0;
309
0
    char *start, *end;
310
311
0
    if (zip_os_stat(ctx->fname, &st) == 0) {
312
        /* If the file already exists, we copy permissions on commit, so err on the side of caution. */
313
0
        ctx->temp_output_created = false;
314
0
        mode = 0600;
315
0
    }
316
0
    else {
317
        /* If we create a new file, we want 0666 with umask applied. Since we can't read the current umask, we can't apply that mode on commit, so we set it here instead. */
318
0
        ctx->temp_output_created = true;
319
0
        mode = 0666;
320
0
    }
321
322
0
    size_t temp_size = strlen(ctx->fname) + 13;
323
0
    if ((temp = (char *)malloc(temp_size)) == NULL) {
324
0
        zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0);
325
0
        return -1;
326
0
    }
327
0
    snprintf_s(temp, temp_size, "%s.XXXXXX.part", ctx->fname);
328
0
    end = temp + strlen(temp) - 5;
329
0
    start = end - 6;
330
331
0
    for (;;) {
332
0
        zip_uint32_t value = zip_random_uint32();
333
0
        char *xs = start;
334
335
0
        while (xs < end) {
336
0
            char digit = value % 36;
337
0
            if (digit < 10) {
338
0
                *(xs++) = digit + '0';
339
0
            }
340
0
            else {
341
0
                *(xs++) = digit - 10 + 'a';
342
0
            }
343
0
            value /= 36;
344
0
        }
345
346
0
        if (create_file) {
347
0
            if ((fd = open(temp, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, mode)) >= 0) {
348
0
                break;
349
0
            }
350
0
            if (errno != EEXIST) {
351
0
                zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
352
0
                free(temp);
353
0
                return -1;
354
0
            }
355
0
        }
356
0
        else {
357
0
            if (zip_os_stat(temp, &st) < 0) {
358
0
                if (errno == ENOENT) {
359
0
                    break;
360
0
                }
361
0
                else {
362
0
                    zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno);
363
0
                    free(temp);
364
0
                    return -1;
365
0
                }
366
0
            }
367
0
        }
368
0
    }
369
370
0
    ctx->tmpname = temp;
371
372
0
    return fd; /* initialized to 0 if !create_file */
373
0
}
374
375
376
/*
377
 * fopen replacement that sets the close-on-exec flag
378
 * some implementations support an fopen 'e' flag for that,
379
 * but e.g. macOS doesn't.
380
 */
381
8.78k
static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable) {
382
8.78k
    int fd;
383
8.78k
    int flags;
384
8.78k
    FILE *fp;
385
386
8.78k
    flags = O_CLOEXEC;
387
8.78k
    if (writeable) {
388
0
        flags |= O_RDWR;
389
0
    }
390
8.78k
    else {
391
8.78k
        flags |= O_RDONLY;
392
8.78k
    }
393
394
    /* mode argument needed on Windows */
395
8.78k
    if ((fd = open(name, flags, 0666)) < 0) {
396
0
        return NULL;
397
0
    }
398
8.78k
    if ((fp = fdopen(fd, writeable ? "r+b" : "rb")) == NULL) {
399
0
        close(fd);
400
0
        return NULL;
401
0
    }
402
8.78k
    return fp;
403
8.78k
}
404
405
0
static bool copy_permissions(zip_source_file_context_t *ctx) {
406
0
    zip_os_stat_t st;
407
0
    bool ok = true;
408
409
    /* Prefer fstat over stat when possible. */
410
0
    if (ctx->f != NULL) {
411
0
        if (zip_os_fstat(fileno((FILE *)ctx->f), &st) < 0) {
412
0
            ok = false;
413
0
        }
414
0
    }
415
0
    else {
416
0
        if (zip_os_stat(ctx->fname, &st) < 0) {
417
0
            ok = false;
418
0
        }
419
0
    }
420
0
    if (!ok) {
421
0
        zip_error_set(&ctx->error, ZIP_ER_CLOSE, errno);
422
0
        return false;
423
0
    }
424
425
    /* Use file descriptor and not file name to avoid an exploitable race condition. */
426
0
    if (fchmod(fileno((FILE *)ctx->fout), st.st_mode) < 0) {
427
0
        zip_error_set(&ctx->error, ZIP_ER_CLOSE, errno);
428
0
        return false;
429
0
    }
430
431
    /* TODO: copy ACLs */
432
433
0
    return true;
434
0
}