• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 26735862306

31 May 2026 11:43PM UTC coverage: 89.37%. Remained the same
26735862306

push

github

web-flow
Merge pull request #5631 from randombit/jack/overflow-checks

Systematically eliminate any possible integer overflows

110295 of 123414 relevant lines covered (89.37%)

11075877.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.73
/src/cli/zfec.cpp
1
/*
2
* (C) 2021 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "cli.h"
8

9
#if defined(BOTAN_HAS_ZFEC) && defined(BOTAN_HAS_SHA2_64)
10
   #include <botan/hash.h>
11
   #include <botan/mem_ops.h>
12
   #include <botan/zfec.h>
13
   #include <botan/internal/int_utils.h>
14
   #include <botan/internal/loadstor.h>
15
   #include <fstream>
16
   #include <sstream>
17
#endif
18

19
namespace Botan_CLI {
20

21
namespace {
22

23
#if defined(BOTAN_HAS_ZFEC) && defined(BOTAN_HAS_SHA2_64)
24

25
constexpr uint32_t FEC_MAGIC = 0xFECC0DEC;
26
const char* const FEC_SHARE_HASH = "SHA-512-256";
27

28
class FEC_Share final {
34✔
29
   public:
30
      FEC_Share() : m_share(0), m_k(0), m_n(0), m_padding(0) {}
31

32
      FEC_Share(size_t share, size_t k, size_t n, size_t padding, const uint8_t bits[], size_t len) :
15✔
33
            m_share(share), m_k(k), m_n(n), m_padding(padding), m_bits(bits, bits + len) {}
30✔
34

35
      size_t share_id() const { return m_share; }
3✔
36

37
      size_t k() const { return m_k; }
10✔
38

39
      size_t n() const { return m_n; }
10✔
40

41
      size_t padding() const { return m_padding; }
5✔
42

43
      size_t share_size() const { return m_bits.size(); }
3✔
44

45
      const uint8_t* share_data() const { return m_bits.data(); }
3✔
46

47
      static FEC_Share deserialize(const uint8_t bits[], size_t len, Botan::HashFunction& hash) {
10✔
48
         const size_t hash_len = hash.output_length();
10✔
49

50
         if(len < FEC_SHARE_HEADER_LEN + hash_len) {
10✔
51
            throw CLI_Error("FEC share is too short to be valid");
×
52
         }
53

54
         if(Botan::load_be<uint32_t>(bits, 0) != FEC_MAGIC) {
10✔
55
            throw CLI_Error("FEC share does not have expected magic bytes");
×
56
         }
57

58
         // verify that reserved bytes are zero
59
         for(size_t i = 8; i != 12; ++i) {
50✔
60
            if(bits[i] != 0) {
40✔
61
               throw CLI_Error("FEC share has reserved header bytes set");
×
62
            }
63
         }
64

65
         const size_t share_id = bits[4];
10✔
66
         const size_t k = bits[5];
10✔
67
         const size_t n = bits[6];
10✔
68
         const size_t padding = bits[7];
10✔
69

70
         if(share_id >= n || k >= n || padding >= k) {
10✔
71
            throw CLI_Error("FEC share has invalid k/n/padding fields");
×
72
         }
73

74
         hash.update(bits, len - hash_len);
10✔
75
         auto share_hash = hash.final();
10✔
76

77
         const bool digest_ok = Botan::constant_time_compare(share_hash.data(), &bits[len - hash_len], hash_len);
10✔
78

79
         if(!digest_ok) {
10✔
80
            throw CLI_Error("FEC share has invalid hash");
×
81
         }
82

83
         return FEC_Share(
10✔
84
            share_id, k, n, padding, bits + FEC_SHARE_HEADER_LEN, len - (FEC_SHARE_HEADER_LEN + hash_len));
10✔
85
      }
10✔
86

87
      void serialize_to(Botan::HashFunction& hash, std::ostream& out) const {
5✔
88
         uint8_t header[FEC_SHARE_HEADER_LEN] = {0};
5✔
89

90
         Botan::store_be(FEC_MAGIC, header);
5✔
91
         header[4] = static_cast<uint8_t>(m_share);
5✔
92
         header[5] = static_cast<uint8_t>(m_k);
5✔
93
         header[6] = static_cast<uint8_t>(m_n);
5✔
94
         header[7] = static_cast<uint8_t>(m_padding);
5✔
95
         // bytes 8..12 left as zero/reserved
96

97
         out.write(reinterpret_cast<const char*>(header), sizeof(header));
5✔
98
         out.write(reinterpret_cast<const char*>(m_bits.data()), m_bits.size());
5✔
99

100
         hash.update(header, sizeof(header));
5✔
101
         hash.update(m_bits);
5✔
102
         auto digest = hash.final();
5✔
103

104
         out.write(reinterpret_cast<const char*>(digest.data()), digest.size());
5✔
105
      }
5✔
106

107
   private:
108
      static const size_t FEC_SHARE_HEADER_LEN = 12;
109

110
      size_t m_share;
111
      size_t m_k;
112
      size_t m_n;
113
      size_t m_padding;
114
      std::vector<uint8_t> m_bits;
115
};
116

117
class FEC_Encode final : public Command {
118
   public:
119
      FEC_Encode() : Command("fec_encode --suffix=fec --prefix= --output-dir= k n input") {}
4✔
120

121
      std::string group() const override { return "fec"; }
1✔
122

123
      std::string description() const override { return "Forward error encode a file"; }
1✔
124

125
      void go() override {
1✔
126
         const size_t k = get_arg_sz("k");
1✔
127
         const size_t n = get_arg_sz("n");
1✔
128

129
         const std::string suffix = get_arg("suffix");
1✔
130
         const std::string prefix = get_arg("prefix");
1✔
131
         const std::string input = get_arg("input");
1✔
132
         const std::string output_dir = get_arg("output-dir");
1✔
133

134
         const Botan::ZFEC fec(k, n);  // checks k/n for validity
1✔
135

136
         auto hash = Botan::HashFunction::create_or_throw(FEC_SHARE_HASH);
1✔
137

138
         auto input_data = slurp_file(get_arg("input"));
2✔
139

140
         // append a hash of the input
141
         hash->update(input_data);
1✔
142
         const auto hash_of_input = hash->final();
1✔
143
         input_data.insert(input_data.end(), hash_of_input.begin(), hash_of_input.end());
1✔
144

145
         // add padding 0x00 bytes as needed to round up to k multiple
146
         size_t padding = 0;
1✔
147
         while(input_data.size() % k != 0) {
2✔
148
            padding += 1;
1✔
149
            input_data.push_back(0x00);
1✔
150
         }
151

152
         auto encoder_fn = [&](size_t share, const uint8_t bits[], size_t len) {
6✔
153
            std::ostringstream output_fsname;
5✔
154

155
            if(!output_dir.empty()) {
5✔
156
               output_fsname << output_dir << "/";
5✔
157
            }
158

159
            if(!prefix.empty()) {
5✔
160
               output_fsname << prefix;
5✔
161
            } else {
162
               output_fsname << input;
×
163
            }
164

165
            output_fsname << "." << (share + 1) << "_" << n;
5✔
166

167
            if(!suffix.empty()) {
5✔
168
               output_fsname << "." << suffix;
5✔
169
            }
170

171
            std::ofstream output(output_fsname.str(), std::ios::binary);
5✔
172

173
            const FEC_Share fec_share(share, k, n, padding, bits, len);
5✔
174
            fec_share.serialize_to(*hash, output);
5✔
175
         };
5✔
176

177
         fec.encode(input_data.data(), input_data.size(), encoder_fn);
2✔
178
      }
4✔
179
};
180

181
BOTAN_REGISTER_COMMAND("fec_encode", FEC_Encode);
2✔
182

183
class FEC_Decode final : public Command {
184
   public:
185
      FEC_Decode() : Command("fec_decode *shares") {}
6✔
186

187
      std::string group() const override { return "fec"; }
1✔
188

189
      std::string description() const override { return "Recover data from FEC shares"; }
1✔
190

191
      void go() override {
2✔
192
         auto hash = Botan::HashFunction::create_or_throw(FEC_SHARE_HASH);
2✔
193
         const size_t hash_len = hash->output_length();
2✔
194

195
         std::vector<FEC_Share> shares;
2✔
196

197
         for(const auto& share_fsname : get_arg_list("shares")) {
7✔
198
            const auto share_bits = slurp_file(share_fsname);
5✔
199

200
            try {
5✔
201
               auto share = FEC_Share::deserialize(share_bits.data(), share_bits.size(), *hash);
5✔
202
               shares.push_back(share);
5✔
203
            } catch(std::exception& e) {
5✔
204
               error_output() << "Ignoring invalid share '" << share_fsname << "': " << e.what() << "\n";
×
205
            }
×
206
         }
7✔
207

208
         if(shares.empty()) {
2✔
209
            error_output() << "Must provide a list of at least k shares\n";
×
210
            this->set_return_code(1);
×
211
            return;
×
212
         }
213

214
         size_t k = 0;
2✔
215
         size_t n = 0;
2✔
216
         size_t padding = 0;
2✔
217
         size_t share_size = 0;
2✔
218

219
         for(const auto& share : shares) {
7✔
220
            if(k == 0 && n == 0 && padding == 0) {
5✔
221
               k = share.k();
2✔
222
               n = share.n();
2✔
223
               padding = share.padding();
2✔
224
               share_size = share.share_size();
2✔
225
            } else {
226
               if(share.k() != k || share.n() != n || share.padding() != padding || share.share_size() != share_size) {
3✔
227
                  error_output() << "Shares have mismatched k/n/padding/size values\n";
×
228
                  this->set_return_code(2);
×
229
                  return;
×
230
               }
231
            }
232
         }
233

234
         if(shares.size() < k) {
2✔
235
            error_output() << "At least " << k << " shares are required for recovery\n";
1✔
236
            this->set_return_code(2);
1✔
237
            return;
1✔
238
         }
239

240
         const Botan::ZFEC fec(k, n);
1✔
241

242
         const auto decoded_size = Botan::checked_mul(share_size, k);
1✔
243
         if(!decoded_size.has_value()) {
1✔
244
            throw CLI_Error("Share size and count are too large to decode");
×
245
         }
246

247
         const size_t decoded_len = decoded_size.value();
1✔
248
         const size_t trailer_len = Botan::add_or_throw(hash_len, padding, "FEC share padding is too large");
1✔
249
         if(decoded_len < trailer_len) {
1✔
250
            throw CLI_Error("Recovered data is too short to be valid");
×
251
         }
252

253
         std::vector<uint8_t> decoded(decoded_len);
1✔
254

255
         auto decoder_fn = [&](size_t share, const uint8_t bits[], size_t len) {
4✔
256
            std::memcpy(&decoded[share * share_size], bits, len);
3✔
257
         };
4✔
258

259
         std::map<size_t, const uint8_t*> share_ptrs;
1✔
260

261
         for(auto& share : shares) {
4✔
262
            share_ptrs[share.share_id()] = share.share_data();
3✔
263
         }
264

265
         fec.decode_shares(share_ptrs, share_size, decoder_fn);
1✔
266

267
         const size_t output_len = decoded_len - trailer_len;
1✔
268
         auto decoded_digest = hash->process(decoded.data(), output_len);
1✔
269

270
         if(!Botan::constant_time_compare(decoded_digest.data(), &decoded[output_len], hash_len)) {
1✔
271
            throw CLI_Error("Recovered data failed digest check");
×
272
         }
273

274
         for(size_t i = 0; i != padding; ++i) {
2✔
275
            if(decoded[decoded_len - padding + i] != 0) {
1✔
276
               throw CLI_Error("Recovered data had non-zero padding bytes");
×
277
            }
278
         }
279

280
         output_binary().write(reinterpret_cast<const char*>(decoded.data()), output_len);
1✔
281
      }
6✔
282
};
283

284
BOTAN_REGISTER_COMMAND("fec_decode", FEC_Decode);
3✔
285

286
class FEC_Info final : public Command {
287
   public:
288
      FEC_Info() : Command("fec_info share") {}
12✔
289

290
      std::string group() const override { return "fec"; }
1✔
291

292
      std::string description() const override { return "Display information about a FEC share"; }
1✔
293

294
      void go() override {
5✔
295
         auto hash = Botan::HashFunction::create_or_throw(FEC_SHARE_HASH);
5✔
296

297
         const std::string share_fsname = get_arg("share");
5✔
298
         const auto share_bits = slurp_file(share_fsname);
5✔
299

300
         try {
5✔
301
            auto share = FEC_Share::deserialize(share_bits.data(), share_bits.size(), *hash);
5✔
302
            output() << "FEC share " << share.share_id() + 1 << "/" << share.n() << " with " << share.k()
5✔
303
                     << " needed for recovery\n";
5✔
304
         } catch(std::exception& e) {
5✔
305
            error_output() << "Invalid share '" << share_fsname << "': " << e.what() << "\n";
×
306
         }
×
307
      }
10✔
308
};
309

310
BOTAN_REGISTER_COMMAND("fec_info", FEC_Info);
6✔
311

312
#endif
313

314
}  // namespace
315

316
}  // namespace Botan_CLI
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc