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

mendersoftware / mender / 2218679915

16 Dec 2025 02:09PM UTC coverage: 79.672% (-0.01%) from 79.682%
2218679915

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%)

38 existing lines in 4 files now uncovered.

7870 of 9878 relevant lines covered (79.67%)

13898.57 hits per line

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

75.4
/src/artifact/v3/header/header.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 <artifact/v3/header/header.hpp>
16

17
#include <iomanip>
18
#include <memory>
19
#include <string>
20
#include <system_error>
21
#include <vector>
22
#include <iostream>
23
#include <fstream>
24

25
#include <common/expected.hpp>
26
#include <common/error.hpp>
27
#include <common/io.hpp>
28
#include <common/log.hpp>
29
#include <common/json.hpp>
30
#include <common/common.hpp>
31
#include <common/path.hpp>
32

33
#include <artifact/error.hpp>
34
#include <artifact/lexer.hpp>
35
#include <artifact/tar/tar.hpp>
36

37
#include <artifact/v3/header/token.hpp>
38

39
namespace mender {
40
namespace artifact {
41
namespace v3 {
42
namespace header {
43

44
using namespace std;
45

46
namespace expected = mender::common::expected;
47
namespace io = mender::common::io;
48
namespace error = mender::common::error;
49
namespace log = mender::common::log;
50
namespace json = mender::common::json;
51
namespace path = mender::common::path;
52

53

54
namespace {
55
string IndexString(int index) {
1✔
56
        stringstream index_string {};
2✔
57
        index_string << setw(4) << setfill('0') << index;
1✔
58
        return index_string.str();
1✔
59
}
60
} // namespace
61

62
ExpectedHeader Parse(io::Reader &reader, ParserConfig conf) {
170✔
63
        Header header {};
340✔
64

65
        shared_ptr<tar::Reader> tar_reader {make_shared<tar::Reader>(reader)};
170✔
66

67
        auto lexer = lexer::Lexer<header::token::Token, header::token::Type> {tar_reader};
340✔
68

69
        token::Token tok = lexer.Next();
340✔
70

71
        if (tok.type != token::Type::HeaderInfo) {
170✔
72
                return expected::unexpected(parser_error::MakeError(
1✔
73
                        parser_error::Code::ParseError,
74
                        "Got unexpected token: '" + tok.TypeToString() + "' expected 'header-info'"));
3✔
75
        }
76

77
        auto expected_info = header::info::Parse(*tok.value);
169✔
78

79
        if (!expected_info) {
169✔
80
                return expected::unexpected(parser_error::MakeError(
×
81
                        parser_error::Code::ParseError,
82
                        "Failed to parse the header-info: " + expected_info.error().message));
×
83
        }
84
        header.info = expected_info.value();
169✔
85

86
        tok = lexer.Next();
169✔
87
        vector<ArtifactScript> state_scripts {};
169✔
88
        if (tok.type == token::Type::ArtifactScripts) {
169✔
89
                if (conf.artifact_scripts_filesystem_path == "") {
72✔
90
                        return expected::unexpected(
×
91
                                parser_error::MakeError(parser_error::NoStateScriptsPathError, ""));
×
92
                }
93
                if (not path::FileExists(conf.artifact_scripts_filesystem_path)) {
72✔
94
                        log::Trace(
67✔
95
                                "Creating the Artifact script directory: " + conf.artifact_scripts_filesystem_path);
67✔
96
                        error::Error err = path::CreateDirectories(conf.artifact_scripts_filesystem_path);
67✔
97
                        if (err != error::NoError) {
67✔
98
                                return expected::unexpected(err.WithContext(
×
99
                                        "Failed to create the scripts directory for installing Artifact scripts"));
×
100
                        }
101
                }
102
        }
103
        while (tok.type == token::Type::ArtifactScripts) {
1,188✔
104
                log::Trace("Parsing state script...");
2,040✔
105
                const string artifact_script_path =
106
                        path::Join(conf.artifact_scripts_filesystem_path, tok.name);
1,020✔
107
                auto exp_path_is_safe =
108
                        path::IsWithinOrEqual(artifact_script_path, conf.artifact_scripts_filesystem_path);
1,020✔
109
                if (!exp_path_is_safe.has_value()) {
1,020✔
110
                        return expected::unexpected(exp_path_is_safe.error().WithContext(
×
111
                                "Error checking if path is equal to or within directory"));
×
112
                }
113
                if (!exp_path_is_safe.value()) {
1,020✔
114
                        return expected::unexpected(parser_error::MakeError(
1✔
115
                                parser_error::Code::ParseError,
116
                                "Error parsing state script: Provided script path is outside state script directory."));
3✔
117
                }
118
                errno = 0;
1,019✔
119
                ofstream myfile(artifact_script_path);
2,038✔
120
                log::Trace("state script name: " + tok.name);
2,038✔
121
                if (!myfile.good()) {
1,019✔
122
                        auto io_errno = errno;
×
123
                        return expected::unexpected(error::Error(
×
124
                                std::generic_category().default_error_condition(io_errno),
×
125
                                "Failed to create a file for writing the Artifact script: " + artifact_script_path
×
126
                                        + " to the filesystem"));
×
127
                }
128
                io::StreamWriter sw {myfile};
2,038✔
129

130
                auto err = io::Copy(sw, *tok.value);
1,019✔
131
                if (err != error::NoError) {
1,019✔
132
                        return expected::unexpected(err);
×
133
                }
134

135
                state_scripts.push_back(artifact_script_path);
1,019✔
136

137
                // Set the permissions on the installed Artifact scripts
138
                err = path::Permissions(
1,019✔
139
                        artifact_script_path,
140
                        {path::Perms::Owner_read, path::Perms::Owner_write, path::Perms::Owner_exec});
2,038✔
141
                if (err != error::NoError) {
1,019✔
142
                        return expected::unexpected(err);
×
143
                }
144

145
                tok = lexer.Next();
1,019✔
146
        }
147

148
        if (state_scripts.size() > 0) {
168✔
149
                // Write the Artifact script version file
150
                const string artifact_script_version_file =
151
                        path::Join(conf.artifact_scripts_filesystem_path, "version");
71✔
152
                errno = 0;
71✔
153
                ofstream myfile(artifact_script_version_file);
142✔
154
                log::Trace("Creating the Artifact script version file: " + artifact_script_version_file);
142✔
155
                if (!myfile.good()) {
71✔
156
                        auto io_errno = errno;
×
157
                        return expected::unexpected(error::Error(
×
158
                                std::generic_category().default_error_condition(io_errno),
×
159
                                "Failed to create the Artifact script version file: "
160
                                        + artifact_script_version_file));
×
161
                }
162
                myfile << to_string(conf.artifact_scripts_version);
142✔
163
                if (!myfile.good()) {
71✔
164
                        auto io_errno = errno;
×
165
                        return expected::unexpected(error::Error(
×
166
                                std::generic_category().default_error_condition(io_errno),
×
167
                                "I/O error writing the Artifact scripts version file"));
×
168
                }
169

170
                // Sync the directory so we know it is permanent.
171
                auto err = path::DataSyncRecursively(conf.artifact_scripts_filesystem_path);
71✔
172
                if (err != error::NoError) {
71✔
173
                        return expected::unexpected(err.WithContext("While syncing artifact script directory"));
×
174
                }
175
        }
176

177

178
        header.artifactScripts = std::move(state_scripts);
168✔
179

180
        vector<SubHeader> subheaders {};
168✔
181

182
        int current_index {0};
183
        while (tok.type != token::Type::EOFToken and tok.type != token::Type::Unrecognized) {
331✔
184
                log::Trace("Parsing the sub-header ...");
336✔
185

186
                // NOTE: We currently do not support multiple payloads
187
                if (current_index != 0) {
168✔
188
                        return expected::unexpected(parser_error::MakeError(
2✔
189
                                parser_error::Code::ParseError,
190
                                "Multiple header entries found. Currently only one is supported"));
6✔
191
                }
192

193
                // Special check for malformed artifact that does not list the payload in the header.
194
                if (header.info.payloads.size() == 0) {
166✔
195
                        return expected::unexpected(parser_error::MakeError(
2✔
196
                                parser_error::Code::ParseError,
197
                                "Unsupported number of payloads defined in header: "
198
                                        + to_string(header.info.payloads.size())));
6✔
199
                }
200

201
                SubHeader sub_header {};
327✔
202
                if (tok.type != token::Type::ArtifactHeaderTypeInfo) {
164✔
UNCOV
203
                        return expected::unexpected(parser_error::MakeError(
×
204
                                parser_error::Code::ParseError,
UNCOV
205
                                "Unexpected entry: " + tok.TypeToString() + " expected: type-info"));
×
206
                }
207

208
                if (current_index != tok.Index()) {
164✔
209
                        return expected::unexpected(parser_error::MakeError(
1✔
210
                                parser_error::Code::ParseError,
211
                                "Unexpected index order for the type-info: " + tok.name + " expected: headers/"
2✔
212
                                        + IndexString(current_index) + "/type-info"));
5✔
213
                }
214
                auto expected_type_info = type_info::Parse(*tok.value);
163✔
215
                if (!expected_type_info) {
163✔
UNCOV
216
                        return expected::unexpected(expected_type_info.error());
×
217
                }
218
                sub_header.type_info = expected_type_info.value();
163✔
219

220
                // NOTE (workaround): Bug in the Artifact format writer:
221
                // If the type is a RootfsImage, then the payload-type will be empty. This
222
                // is a bug in the mender-artifact tool, which writes the payload. For now,
223
                // just work around it.
224
                if (header.info.payloads[current_index].type == Payload::RootfsImage) {
163✔
225
                        log::Debug(
73✔
226
                                "Setting the type-info in payload nr " + to_string(current_index)
146✔
227
                                + " to rootfs-image");
146✔
228
                        sub_header.type_info.type = "rootfs-image";
73✔
229
                }
230

231
                tok = lexer.Next();
163✔
232

233
                log::Trace("sub-header: looking for meta-data");
326✔
234

235
                // meta-data (optional)
236
                if (tok.type == token::Type::ArtifactHeaderMetaData) {
163✔
237
                        if (current_index != tok.Index()) {
72✔
UNCOV
238
                                return expected::unexpected(parser_error::MakeError(
×
239
                                        parser_error::Code::ParseError,
UNCOV
240
                                        "Unexpected index order for the meta-data: " + tok.name + " expected: headers/"
×
UNCOV
241
                                                + IndexString(current_index) + "/meta-data"));
×
242
                        }
243
                        auto expected_meta_data = meta_data::Parse(*tok.value);
72✔
244
                        if (!expected_meta_data) {
72✔
UNCOV
245
                                return expected::unexpected(expected_meta_data.error());
×
246
                        }
247
                        sub_header.metadata = expected_meta_data.value();
72✔
248
                        tok = lexer.Next();
72✔
249
                }
250
                log::Trace("sub-header: parsed the meta-data");
326✔
251

252
                header.subHeaders.push_back(sub_header);
163✔
253

254
                current_index++;
255
        }
256

257
        if (header.info.payloads.size() != header.subHeaders.size()) {
163✔
258
                return expected::unexpected(parser_error::MakeError(
2✔
259
                        parser_error::Code::ParseError,
260
                        "Header's type-info files number not equal to payloads number"));
6✔
261
        }
262

263
        if (tok.type == token::Type::Unrecognized) {
161✔
264
                return expected::unexpected(parser_error::MakeError(
1✔
265
                        parser_error::Code::ParseError, "Unrecognized error while parsing the header"));
3✔
266
        }
267

268
        return header;
160✔
269
}
270

271
} // namespace header
272
} // namespace v3
273
} // namespace artifact
274
} // 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