Coverage Report

Created: 2026-10-17 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/git/pack-check.c
Line
Count
Source
1
#define DISABLE_SIGN_COMPARE_WARNINGS
2
3
#include "git-compat-util.h"
4
#include "environment.h"
5
#include "hex.h"
6
#include "repository.h"
7
#include "pack.h"
8
#include "progress.h"
9
#include "packfile.h"
10
#include "object-file.h"
11
#include "odb.h"
12
#include "odb/streaming.h"
13
14
struct idx_entry {
15
  off_t                offset;
16
  unsigned int nr;
17
};
18
19
static int compare_entries(const void *e1, const void *e2)
20
0
{
21
0
  const struct idx_entry *entry1 = e1;
22
0
  const struct idx_entry *entry2 = e2;
23
0
  if (entry1->offset < entry2->offset)
24
0
    return -1;
25
0
  if (entry1->offset > entry2->offset)
26
0
    return 1;
27
0
  return 0;
28
0
}
29
30
int check_pack_crc(struct packed_git *p, struct pack_window **w_curs,
31
       off_t offset, off_t len, unsigned int nr)
32
0
{
33
0
  const uint32_t *index_crc;
34
0
  uint32_t data_crc = crc32(0, NULL, 0);
35
36
0
  do {
37
0
    size_t avail;
38
0
    void *data = use_pack(p, w_curs, offset, &avail);
39
0
    if (avail > len)
40
0
      avail = len;
41
0
    data_crc = crc32(data_crc, data, avail);
42
0
    offset += avail;
43
0
    len -= avail;
44
0
  } while (len);
45
46
0
  index_crc = p->index_data;
47
0
  index_crc += 2 + 256 + (size_t)p->num_objects * (p->repo->hash_algo->rawsz/4) + nr;
48
49
0
  return data_crc != ntohl(*index_crc);
50
0
}
51
52
static int verify_packfile(struct repository *r,
53
         struct packed_git *p,
54
         struct pack_window **w_curs,
55
         verify_fn fn,
56
         void *fn_data,
57
         struct progress *progress, uint32_t base_count)
58
59
0
{
60
0
  off_t index_size = p->index_size;
61
0
  const unsigned char *index_base = p->index_data;
62
0
  struct git_hash_ctx ctx;
63
0
  unsigned char hash[GIT_MAX_RAWSZ], *pack_sig;
64
0
  off_t offset = 0, pack_sig_ofs = 0;
65
0
  uint32_t nr_objects, i;
66
0
  int err = 0;
67
0
  struct idx_entry *entries;
68
69
0
  if (!is_pack_valid(p))
70
0
    return error("packfile %s cannot be accessed", p->pack_name);
71
72
0
  git_hash_init(&ctx, r->hash_algo);
73
0
  do {
74
0
    size_t remaining;
75
0
    unsigned char *in = use_pack(p, w_curs, offset, &remaining);
76
0
    offset += remaining;
77
0
    if (!pack_sig_ofs)
78
0
      pack_sig_ofs = p->pack_size - r->hash_algo->rawsz;
79
0
    if (offset > pack_sig_ofs)
80
0
      remaining -= (unsigned int)(offset - pack_sig_ofs);
81
0
    git_hash_update(&ctx, in, remaining);
82
0
  } while (offset < pack_sig_ofs);
83
0
  git_hash_final(hash, &ctx);
84
0
  pack_sig = use_pack(p, w_curs, pack_sig_ofs, NULL);
85
0
  if (!hasheq(hash, pack_sig, r->hash_algo))
86
0
    err = error("%s pack checksum mismatch",
87
0
          p->pack_name);
88
0
  if (!hasheq(index_base + index_size - r->hash_algo->hexsz, pack_sig,
89
0
        r->hash_algo))
90
0
    err = error("%s pack checksum does not match its index",
91
0
          p->pack_name);
92
0
  unuse_pack(w_curs);
93
94
  /* Make sure everything reachable from idx is valid.  Since we
95
   * have verified that nr_objects matches between idx and pack,
96
   * we do not do scan-streaming check on the pack file.
97
   */
98
0
  nr_objects = p->num_objects;
99
0
  ALLOC_ARRAY(entries, nr_objects + 1);
100
0
  entries[nr_objects].offset = pack_sig_ofs;
101
  /* first sort entries by pack offset, since unpacking them is more efficient that way */
102
0
  for (i = 0; i < nr_objects; i++) {
103
0
    entries[i].offset = nth_packed_object_offset(p, i);
104
0
    entries[i].nr = i;
105
0
  }
106
0
  QSORT(entries, nr_objects, compare_entries);
107
108
0
  for (i = 0; i < nr_objects; i++) {
109
0
    struct odb_stream *stream = NULL;
110
0
    void *data;
111
0
    struct object_id oid;
112
0
    enum object_type type;
113
0
    size_t size;
114
0
    off_t curpos;
115
0
    int data_valid;
116
117
0
    if (nth_packed_object_id(&oid, p, entries[i].nr) < 0)
118
0
      BUG("unable to get oid of object %lu from %s",
119
0
          (unsigned long)entries[i].nr, p->pack_name);
120
121
0
    if (p->index_version > 1) {
122
0
      off_t offset = entries[i].offset;
123
0
      off_t len = entries[i+1].offset - offset;
124
0
      unsigned int nr = entries[i].nr;
125
0
      if (check_pack_crc(p, w_curs, offset, len, nr))
126
0
        err = error("index CRC mismatch for object %s "
127
0
              "from %s at offset %"PRIuMAX"",
128
0
              oid_to_hex(&oid),
129
0
              p->pack_name, (uintmax_t)offset);
130
0
    }
131
132
0
    curpos = entries[i].offset;
133
0
    type = unpack_object_header(p, w_curs, &curpos, &size);
134
0
    unuse_pack(w_curs);
135
136
0
    if (type == OBJ_BLOB &&
137
0
        repo_settings_get_big_file_threshold(r) <= size) {
138
      /*
139
       * Let stream_object_signature() check it with
140
       * the streaming interface; no point slurping
141
       * the data in-core only to discard.
142
       */
143
0
      data = NULL;
144
0
      data_valid = 0;
145
0
    } else {
146
0
      data = unpack_entry(r, p, entries[i].offset, &type,
147
0
              &size);
148
0
      data_valid = 1;
149
0
    }
150
151
0
    if (data_valid && !data)
152
0
      err = error("cannot unpack %s from %s at offset %"PRIuMAX"",
153
0
            oid_to_hex(&oid), p->pack_name,
154
0
            (uintmax_t)entries[i].offset);
155
0
    else if (data && check_object_signature(r, &oid, data, size,
156
0
              type) < 0)
157
0
      err = error("packed %s from %s is corrupt",
158
0
            oid_to_hex(&oid), p->pack_name);
159
0
    else if (!data &&
160
0
       (packfile_read_object_stream(&stream, &oid, p, entries[i].offset) < 0 ||
161
0
        stream_object_signature(r, stream, &oid) < 0))
162
0
      err = error("packed %s from %s is corrupt",
163
0
            oid_to_hex(&oid), p->pack_name);
164
0
    else if (fn) {
165
0
      int eaten = 0;
166
0
      err |= fn(&oid, type, size, data, &eaten, fn_data);
167
0
      if (eaten)
168
0
        data = NULL;
169
0
    }
170
0
    if (((base_count + i) & 1023) == 0)
171
0
      display_progress(progress, base_count + i);
172
173
0
    if (stream)
174
0
      odb_stream_close(stream);
175
0
    free(data);
176
0
  }
177
178
0
  display_progress(progress, base_count + i);
179
0
  free(entries);
180
0
  return err;
181
0
}
182
183
int verify_pack_index(struct packed_git *p)
184
0
{
185
0
  int err = 0;
186
187
0
  if (open_pack_index(p))
188
0
    return error("packfile %s index not opened", p->pack_name);
189
190
  /* Verify SHA1 sum of the index file */
191
0
  if (!hashfile_checksum_valid(p->repo->hash_algo, p->index_data, p->index_size))
192
0
    err = error("Packfile index for %s hash mismatch",
193
0
          p->pack_name);
194
0
  return err;
195
0
}
196
197
int verify_pack(struct repository *r, struct packed_git *p, verify_fn fn, void *fn_data,
198
    struct progress *progress, uint32_t base_count)
199
0
{
200
0
  int err = 0;
201
0
  struct pack_window *w_curs = NULL;
202
203
0
  err |= verify_pack_index(p);
204
0
  if (!p->index_data)
205
0
    return -1;
206
207
0
  err |= verify_packfile(r, p, &w_curs, fn, fn_data, progress, base_count);
208
0
  unuse_pack(&w_curs);
209
210
0
  return err;
211
0
}