Coverage Report

Created: 2026-03-31 06:24

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
    unsigned long 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
         struct progress *progress, uint32_t base_count)
57
58
0
{
59
0
  off_t index_size = p->index_size;
60
0
  const unsigned char *index_base = p->index_data;
61
0
  struct git_hash_ctx ctx;
62
0
  unsigned char hash[GIT_MAX_RAWSZ], *pack_sig;
63
0
  off_t offset = 0, pack_sig_ofs = 0;
64
0
  uint32_t nr_objects, i;
65
0
  int err = 0;
66
0
  struct idx_entry *entries;
67
68
0
  if (!is_pack_valid(p))
69
0
    return error("packfile %s cannot be accessed", p->pack_name);
70
71
0
  r->hash_algo->init_fn(&ctx);
72
0
  do {
73
0
    unsigned long remaining;
74
0
    unsigned char *in = use_pack(p, w_curs, offset, &remaining);
75
0
    offset += remaining;
76
0
    if (!pack_sig_ofs)
77
0
      pack_sig_ofs = p->pack_size - r->hash_algo->rawsz;
78
0
    if (offset > pack_sig_ofs)
79
0
      remaining -= (unsigned int)(offset - pack_sig_ofs);
80
0
    git_hash_update(&ctx, in, remaining);
81
0
  } while (offset < pack_sig_ofs);
82
0
  git_hash_final(hash, &ctx);
83
0
  pack_sig = use_pack(p, w_curs, pack_sig_ofs, NULL);
84
0
  if (!hasheq(hash, pack_sig, r->hash_algo))
85
0
    err = error("%s pack checksum mismatch",
86
0
          p->pack_name);
87
0
  if (!hasheq(index_base + index_size - r->hash_algo->hexsz, pack_sig,
88
0
        r->hash_algo))
89
0
    err = error("%s pack checksum does not match its index",
90
0
          p->pack_name);
91
0
  unuse_pack(w_curs);
92
93
  /* Make sure everything reachable from idx is valid.  Since we
94
   * have verified that nr_objects matches between idx and pack,
95
   * we do not do scan-streaming check on the pack file.
96
   */
97
0
  nr_objects = p->num_objects;
98
0
  ALLOC_ARRAY(entries, nr_objects + 1);
99
0
  entries[nr_objects].offset = pack_sig_ofs;
100
  /* first sort entries by pack offset, since unpacking them is more efficient that way */
101
0
  for (i = 0; i < nr_objects; i++) {
102
0
    entries[i].offset = nth_packed_object_offset(p, i);
103
0
    entries[i].nr = i;
104
0
  }
105
0
  QSORT(entries, nr_objects, compare_entries);
106
107
0
  for (i = 0; i < nr_objects; i++) {
108
0
    struct odb_read_stream *stream = NULL;
109
0
    void *data;
110
0
    struct object_id oid;
111
0
    enum object_type type;
112
0
    unsigned long size;
113
0
    off_t curpos;
114
0
    int data_valid;
115
116
0
    if (nth_packed_object_id(&oid, p, entries[i].nr) < 0)
117
0
      BUG("unable to get oid of object %lu from %s",
118
0
          (unsigned long)entries[i].nr, p->pack_name);
119
120
0
    if (p->index_version > 1) {
121
0
      off_t offset = entries[i].offset;
122
0
      off_t len = entries[i+1].offset - offset;
123
0
      unsigned int nr = entries[i].nr;
124
0
      if (check_pack_crc(p, w_curs, offset, len, nr))
125
0
        err = error("index CRC mismatch for object %s "
126
0
              "from %s at offset %"PRIuMAX"",
127
0
              oid_to_hex(&oid),
128
0
              p->pack_name, (uintmax_t)offset);
129
0
    }
130
131
0
    curpos = entries[i].offset;
132
0
    type = unpack_object_header(p, w_curs, &curpos, &size);
133
0
    unuse_pack(w_curs);
134
135
0
    if (type == OBJ_BLOB &&
136
0
        repo_settings_get_big_file_threshold(r) <= size) {
137
      /*
138
       * Let stream_object_signature() check it with
139
       * the streaming interface; no point slurping
140
       * the data in-core only to discard.
141
       */
142
0
      data = NULL;
143
0
      data_valid = 0;
144
0
    } else {
145
0
      data = unpack_entry(r, p, entries[i].offset, &type, &size);
146
0
      data_valid = 1;
147
0
    }
148
149
0
    if (data_valid && !data)
150
0
      err = error("cannot unpack %s from %s at offset %"PRIuMAX"",
151
0
            oid_to_hex(&oid), p->pack_name,
152
0
            (uintmax_t)entries[i].offset);
153
0
    else if (data && check_object_signature(r, &oid, data, size,
154
0
              type) < 0)
155
0
      err = error("packed %s from %s is corrupt",
156
0
            oid_to_hex(&oid), p->pack_name);
157
0
    else if (!data &&
158
0
       (packfile_read_object_stream(&stream, &oid, p, entries[i].offset) < 0 ||
159
0
        stream_object_signature(r, stream, &oid) < 0))
160
0
      err = error("packed %s from %s is corrupt",
161
0
            oid_to_hex(&oid), p->pack_name);
162
0
    else if (fn) {
163
0
      int eaten = 0;
164
0
      err |= fn(&oid, type, size, data, &eaten);
165
0
      if (eaten)
166
0
        data = NULL;
167
0
    }
168
0
    if (((base_count + i) & 1023) == 0)
169
0
      display_progress(progress, base_count + i);
170
171
0
    if (stream)
172
0
      odb_read_stream_close(stream);
173
0
    free(data);
174
0
  }
175
176
0
  display_progress(progress, base_count + i);
177
0
  free(entries);
178
0
  return err;
179
0
}
180
181
int verify_pack_index(struct packed_git *p)
182
0
{
183
0
  int err = 0;
184
185
0
  if (open_pack_index(p))
186
0
    return error("packfile %s index not opened", p->pack_name);
187
188
  /* Verify SHA1 sum of the index file */
189
0
  if (!hashfile_checksum_valid(p->repo->hash_algo, p->index_data, p->index_size))
190
0
    err = error("Packfile index for %s hash mismatch",
191
0
          p->pack_name);
192
0
  return err;
193
0
}
194
195
int verify_pack(struct repository *r, struct packed_git *p, verify_fn fn,
196
    struct progress *progress, uint32_t base_count)
197
0
{
198
0
  int err = 0;
199
0
  struct pack_window *w_curs = NULL;
200
201
0
  err |= verify_pack_index(p);
202
0
  if (!p->index_data)
203
0
    return -1;
204
205
0
  err |= verify_packfile(r, p, &w_curs, fn, progress, base_count);
206
0
  unuse_pack(&w_curs);
207
208
0
  return err;
209
0
}