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

mendersoftware / mender / 2217914841

16 Dec 2025 02:09PM UTC coverage: 79.682% (-0.05%) from 79.734%
2217914841

push

gitlab-ci

web-flow
Merge pull request #1863 from michalkopczan/MEN-9098-out-of-bounds-check

fix: Sanitize header list of payloads and corresponding type-info files

8 of 12 new or added lines in 3 files covered. (66.67%)

33 existing lines in 1 file now uncovered.

7871 of 9878 relevant lines covered (79.68%)

13898.54 hits per line

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

80.75
/src/mender-update/update_module/v3/update_module_download.cpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#include <mender-update/update_module/v3/update_module.hpp>
16

17
#include <mender-update/progress_reader/progress_reader.hpp>
18

19
#include <common/events.hpp>
20
#include <common/events_io.hpp>
21
#include <common/log.hpp>
22
#include <common/path.hpp>
23
#include <common/processes.hpp>
24

25
namespace mender {
26
namespace update {
27
namespace update_module {
28
namespace v3 {
29

30
namespace log = mender::common::log;
31
namespace path = mender::common::path;
32
namespace processes = mender::common::processes;
33
namespace progress = mender::update::progress;
34

35

36
void UpdateModule::StartDownloadProcess() {
118✔
37
        string download_command = "Download";
118✔
38
        if (download_->downloading_with_sizes_) {
118✔
39
                download_command = "DownloadWithFileSizes";
3✔
40
        }
41
        log::Debug(
118✔
42
                "Calling Update Module with command `" + update_module_path_ + " " + download_command + " "
236✔
43
                + update_module_workdir_ + "`.");
354✔
44
        download_->proc_ = make_shared<procs::Process>(
118✔
45
                vector<string> {update_module_path_, download_command, update_module_workdir_});
590✔
46

47
        download_->proc_->SetWorkDir(update_module_workdir_);
48

49
        auto err = PrepareStreamNextPipe();
118✔
50
        if (err != error::NoError) {
118✔
51
                DownloadErrorHandler(err);
×
52
                return;
53
        }
54

55
        processes::OutputHandler stdout_handler {"Update Module output (stdout): "};
118✔
56
        processes::OutputHandler stderr_handler {"Update Module output (stderr): "};
118✔
57

58
        err = download_->proc_->Start(stdout_handler, stderr_handler);
354✔
59
        if (err != error::NoError) {
118✔
60
                DownloadErrorHandler(GetProcessError(err));
×
61
                return;
×
62
        }
63

64
        err = download_->proc_->AsyncWait(
118✔
65
                download_->event_loop_,
66
                [this](error::Error err) {
232✔
67
                        if (err.code == make_error_condition(errc::timed_out)) {
116✔
68
                                DownloadTimeoutHandler();
2✔
69
                        } else {
70
                                ProcessEndedHandler(err);
228✔
71
                        }
72
                },
116✔
73
                chrono::seconds(ctx_.GetConfig().module_timeout_seconds));
118✔
74
        if (err != error::NoError) {
118✔
75
                DownloadErrorHandler(err);
×
76
                return;
77
        }
78

79
        DownloadErrorHandler(OpenStreamNextPipe(
236✔
80
                [this](io::ExpectedAsyncWriterPtr writer) { StreamNextOpenHandler(writer); }));
254✔
81
}
82

83
void UpdateModule::StreamNextOpenHandler(io::ExpectedAsyncWriterPtr writer) {
13✔
84
        if (!writer) {
13✔
85
                DownloadErrorHandler(writer.error());
×
86
                return;
3✔
87
        }
88
        download_->stream_next_writer_ = writer.value();
13✔
89

90
        download_->module_has_started_download_ = true;
13✔
91

92
        auto reader = download_->payload_.Next();
13✔
93
        if (!reader) {
13✔
94
                if (reader.error().code
3✔
95
                        == artifact::parser_error::MakeError(
3✔
96
                                   artifact::parser_error::NoMorePayloadFilesError, "")
6✔
97
                                   .code) {
98
                        download_->module_has_finished_download_ = true;
3✔
99
                        log::Debug("Update Module finished all downloads");
6✔
100
                        EndStreamNext();
3✔
101
                } else {
102
                        DownloadErrorHandler(reader.error());
×
103
                }
104
                return;
3✔
105
        }
106
        auto payload_reader = make_shared<artifact::Reader>(std::move(reader.value()));
10✔
107

108
        auto progress_reader = make_shared<progress::Reader>(payload_reader, payload_reader->Size());
10✔
109

110
        download_->current_payload_reader_ =
111
                make_shared<events::io::AsyncReaderFromReader>(download_->event_loop_, progress_reader);
20✔
112
        download_->current_payload_name_ = payload_reader->Name();
20✔
113
        download_->current_payload_size_ = payload_reader->Size();
10✔
114

115
        auto stream_path =
116
                path::Join(update_module_workdir_, string("streams"), download_->current_payload_name_);
20✔
117
        auto exp_path_is_safe =
118
                path::IsWithinOrEqual(stream_path, path::Join(update_module_workdir_, string("streams")));
20✔
119
        if (!exp_path_is_safe.has_value()) {
10✔
120
                DownloadErrorHandler(exp_path_is_safe.error().WithContext(
×
UNCOV
121
                        "Error checking if path is equal to or within directory"));
×
UNCOV
122
                return;
×
123
        }
124
        if (!exp_path_is_safe.value()) {
10✔
UNCOV
125
                DownloadErrorHandler(error::Error(
×
UNCOV
126
                        make_error_condition(errc::invalid_argument),
×
UNCOV
127
                        "Error downloading payload: Provided payload file (" + download_->current_payload_name_
×
UNCOV
128
                                + ") would point outside work directory when extracted."));
×
UNCOV
129
                return;
×
130
        }
131

132
        auto err = PrepareAndOpenStreamPipe(
133
                stream_path, [this](io::ExpectedAsyncWriterPtr writer) { StreamOpenHandler(writer); });
22✔
134
        if (err != error::NoError) {
10✔
UNCOV
135
                DownloadErrorHandler(err);
×
136
                return;
137
        }
138

139
        string stream_next_string;
140
        if (download_->downloading_with_sizes_) {
10✔
141
                stream_next_string = path::Join("streams", download_->current_payload_name_) + " "
2✔
142
                                                         + to_string(download_->current_payload_size_);
4✔
143
        } else {
144
                stream_next_string = path::Join("streams", download_->current_payload_name_);
18✔
145
        }
146
        size_t entry_size = stream_next_string.size() + 1;
10✔
147
        if (entry_size > download_->buffer_.size()) {
10✔
UNCOV
148
                DownloadErrorHandler(error::Error(
×
149
                        make_error_condition(errc::no_buffer_space), "Payload name is too large for buffer"));
×
150
                return;
151
        }
152
        copy(stream_next_string.begin(), stream_next_string.end(), download_->buffer_.begin());
10✔
153
        download_->buffer_[entry_size - 1] = '\n';
10✔
154
        DownloadErrorHandler(download_->stream_next_writer_->AsyncWrite(
10✔
155
                download_->buffer_.begin(),
156
                download_->buffer_.begin() + entry_size,
157
                [this, entry_size](io::ExpectedSize result) {
10✔
158
                        StreamNextWriteHandler(entry_size, result);
10✔
159
                }));
30✔
160
}
161

162
void UpdateModule::StreamOpenHandler(io::ExpectedAsyncWriterPtr writer) {
6✔
163
        if (!writer) {
6✔
UNCOV
164
                DownloadErrorHandler(writer.error());
×
UNCOV
165
                return;
×
166
        }
167
        download_->current_stream_writer_ = writer.value();
6✔
168

169
        DownloadErrorHandler(download_->current_payload_reader_->AsyncRead(
12✔
170
                download_->buffer_.begin(), download_->buffer_.end(), [this](io::ExpectedSize result) {
6✔
171
                        PayloadReadHandler(result);
6✔
172
                }));
18✔
173
}
174

175
void UpdateModule::StreamNextWriteHandler(size_t expected_n, io::ExpectedSize result) {
10✔
176
        // Close stream-next writer.
177
        download_->stream_next_writer_.reset();
10✔
178
        if (!result) {
10✔
179
                DownloadErrorHandler(result.error());
1✔
180
        } else if (expected_n != result.value()) {
9✔
UNCOV
181
                DownloadErrorHandler(error::Error(
×
UNCOV
182
                        make_error_condition(errc::io_error),
×
UNCOV
183
                        "Unexpected number of written bytes to stream-next"));
×
184
        }
185
}
10✔
186

187
void UpdateModule::PayloadReadHandler(io::ExpectedSize result) {
632✔
188
        if (!result) {
632✔
189
                // Close streams.
UNCOV
190
                download_->current_stream_writer_.reset();
×
UNCOV
191
                download_->current_payload_reader_.reset();
×
UNCOV
192
                DownloadErrorHandler(result.error());
×
193
        } else if (result.value() > 0) {
632✔
194
                DownloadErrorHandler(download_->current_stream_writer_->AsyncWrite(
571✔
195
                        download_->buffer_.begin(),
196
                        download_->buffer_.begin() + result.value(),
571✔
197
                        [this, result](io::ExpectedSize write_result) {
1,713✔
198
                                StreamWriteHandler(0, result.value(), write_result);
571✔
199
                        }));
2,284✔
200
        } else {
201
                // Close streams.
202
                download_->current_stream_writer_.reset();
61✔
203
                download_->current_payload_reader_.reset();
61✔
204

205
                if (download_->downloading_to_files_) {
61✔
206
                        StartDownloadToFile();
56✔
207
                } else {
208
                        DownloadErrorHandler(OpenStreamNextPipe(
10✔
209
                                [this](io::ExpectedAsyncWriterPtr writer) { StreamNextOpenHandler(writer); }));
18✔
210
                }
211
        }
212
}
632✔
213

214
void UpdateModule::StreamWriteHandler(size_t offset, size_t expected_n, io::ExpectedSize result) {
571✔
215
        if (!result) {
571✔
216
                DownloadErrorHandler(result.error());
1✔
217
        } else if (result.value() == 0 || result.value() > expected_n) {
570✔
UNCOV
218
                DownloadErrorHandler(error::Error(
×
UNCOV
219
                        make_error_condition(errc::io_error),
×
UNCOV
220
                        "Unexpected number of written bytes to download stream"));
×
221
        } else if (result.value() < expected_n) {
570✔
UNCOV
222
                auto new_offset = offset + result.value();
×
UNCOV
223
                auto new_expected = expected_n - result.value();
×
UNCOV
224
                DownloadErrorHandler(download_->current_stream_writer_->AsyncWrite(
×
225
                        download_->buffer_.begin() + new_offset,
226
                        download_->buffer_.begin() + new_offset + new_expected,
UNCOV
227
                        [this, new_offset, new_expected](io::ExpectedSize write_result) {
×
UNCOV
228
                                StreamWriteHandler(new_offset, new_expected, write_result);
×
UNCOV
229
                        }));
×
230
        } else {
231
                download_->written_ += result.value();
570✔
232
                log::Trace("Wrote " + to_string(download_->written_) + " bytes to Update Module");
1,140✔
233
                DownloadErrorHandler(download_->current_payload_reader_->AsyncRead(
1,140✔
234
                        download_->buffer_.begin(), download_->buffer_.end(), [this](io::ExpectedSize result) {
570✔
235
                                PayloadReadHandler(result);
570✔
236
                        }));
1,710✔
237
        }
238
}
571✔
239

240
void UpdateModule::EndStreamNext() {
3✔
241
        // Empty write.
242
        DownloadErrorHandler(download_->stream_next_writer_->AsyncWrite(
3✔
243
                download_->buffer_.begin(), download_->buffer_.begin(), [this](io::ExpectedSize result) {
6✔
244
                        if (!result) {
3✔
UNCOV
245
                                DownloadErrorHandler(result.error());
×
246
                        } else {
247
                                DownloadErrorHandler(error::NoError);
3✔
248
                        }
249
                        // Close writer.
250
                        download_->stream_next_writer_.reset();
3✔
251
                        // No further action necessary. Now we just need to wait for the process to finish.
252
                }));
6✔
253
}
3✔
254

255
void UpdateModule::DownloadErrorHandler(const error::Error &err) {
1,353✔
256
        if (err != error::NoError) {
1,353✔
257
                EndDownloadLoop(err);
11✔
258
        }
259
}
1,353✔
260

261
void UpdateModule::EndDownloadLoop(const error::Error &err) {
118✔
262
        download_->download_finished_handler_(err);
118✔
263
}
118✔
264

265
void UpdateModule::DownloadTimeoutHandler() {
2✔
266
        download_->proc_->EnsureTerminated();
2✔
267
        EndDownloadLoop(error::Error(
2✔
268
                make_error_condition(errc::timed_out), "Update Module Download process timed out"));
4✔
269
}
2✔
270

271
void UpdateModule::ProcessEndedHandler(error::Error err) {
114✔
272
        if (err != error::NoError) {
114✔
273
                err = GetProcessError(err);
5✔
274
                DownloadErrorHandler(error::Error(
10✔
275
                        err.code, "Download: Update Module returned non-zero status: " + err.message));
10✔
276
        } else if (download_->module_has_finished_download_) {
109✔
277
                EndDownloadLoop(error::NoError);
3✔
278
        } else if (download_->module_has_started_download_) {
106✔
279
                DownloadErrorHandler(error::Error(
2✔
280
                        make_error_condition(errc::broken_pipe),
4✔
281
                        "Update Module started downloading, but did not finish"));
4✔
282
        } else {
283
                download_->downloading_to_files_ = true;
104✔
284
                download_->stream_next_opener_.reset();
104✔
285
                download_->current_stream_opener_.reset();
104✔
286
                err = DeleteStreamsFiles();
104✔
287
                if (err != error::NoError) {
104✔
UNCOV
288
                        DownloadErrorHandler(err);
×
289
                } else {
290
                        StartDownloadToFile();
104✔
291
                }
292
        }
293
}
114✔
294

295
void UpdateModule::StartDownloadToFile() {
160✔
296
        auto reader = download_->payload_.Next();
160✔
297
        if (!reader) {
160✔
298
                if (reader.error().code
102✔
299
                        == artifact::parser_error::MakeError(
102✔
300
                                   artifact::parser_error::NoMorePayloadFilesError, "")
204✔
301
                                   .code) {
302
                        log::Debug("Downloaded all files to `files` directory.");
204✔
303
                        EndDownloadLoop(error::NoError);
102✔
304
                } else {
UNCOV
305
                        DownloadErrorHandler(reader.error());
×
306
                }
307
                return;
102✔
308
        }
309
        auto payload_reader = make_shared<artifact::Reader>(std::move(reader.value()));
58✔
310
        download_->current_payload_reader_ =
311
                make_shared<events::io::AsyncReaderFromReader>(download_->event_loop_, payload_reader);
116✔
312
        download_->current_payload_name_ = payload_reader->Name();
58✔
313

314
        auto stream_path = path::Join(update_module_workdir_, string("files"));
116✔
315
        auto err = PrepareDownloadDirectory(stream_path);
58✔
316
        if (err != error::NoError) {
58✔
UNCOV
317
                DownloadErrorHandler(err);
×
318
                return;
319
        }
320

321
        stream_path = path::Join(stream_path, download_->current_payload_name_);
58✔
322
        auto exp_path_is_safe =
323
                path::IsWithinOrEqual(stream_path, path::Join(update_module_workdir_, string("files")));
116✔
324
        if (!exp_path_is_safe.has_value()) {
58✔
UNCOV
325
                DownloadErrorHandler(exp_path_is_safe.error().WithContext(
×
UNCOV
326
                        "Error checking if path is equal to or within directory"));
×
UNCOV
327
                return;
×
328
        }
329
        if (!exp_path_is_safe.value()) {
58✔
330
                DownloadErrorHandler(error::Error(
1✔
331
                        make_error_condition(errc::invalid_argument),
2✔
332
                        "Error downloading payload: Provided payload file (" + download_->current_payload_name_
1✔
333
                                + ") would point outside work directory when extracted."));
2✔
334
                return;
1✔
335
        }
336

337
        auto current_stream_writer =
338
                make_shared<events::io::AsyncFileDescriptorWriter>(download_->event_loop_);
57✔
339
        err = current_stream_writer->Open(stream_path);
57✔
340
        if (err != error::NoError) {
57✔
341
                DownloadErrorHandler(err);
1✔
342
                return;
343
        }
344
        download_->current_stream_writer_ = current_stream_writer;
345

346
        DownloadErrorHandler(download_->current_payload_reader_->AsyncRead(
112✔
347
                download_->buffer_.begin(), download_->buffer_.end(), [this](io::ExpectedSize result) {
56✔
348
                        PayloadReadHandler(result);
56✔
349
                }));
168✔
350
}
351

352
} // namespace v3
353
} // namespace update_module
354
} // namespace update
355
} // namespace mender
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