Coverage Report

Created: 2025-09-08 08:01

/src/osquery/plugins/config/parsers/file_paths.cpp
Line
Count
Source (jump to first uncovered line)
1
/**
2
 * Copyright (c) 2014-present, The osquery authors
3
 *
4
 * This source code is licensed as defined by the LICENSE file found in the
5
 * root directory of this source tree.
6
 *
7
 * SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
8
 */
9
10
#include <osquery/config/config.h>
11
#include <osquery/filesystem/filesystem.h>
12
#include <osquery/logger/logger.h>
13
#include <osquery/registry/registry_factory.h>
14
#include <osquery/sql/sql.h>
15
16
namespace osquery {
17
18
/**
19
 * @brief A simple ConfigParserPlugin for an "file_paths" dictionary key.
20
 */
21
class FilePathsConfigParserPlugin : public ConfigParserPlugin {
22
 public:
23
2
  virtual ~FilePathsConfigParserPlugin() = default;
24
25
64.6k
  std::vector<std::string> keys() const override {
26
64.6k
    return {"file_paths", "file_paths_query", "file_accesses", "exclude_paths"};
27
64.6k
  }
28
29
  Status setUp() override;
30
31
  Status update(const std::string& source, const ParserConfig& config) override;
32
33
 private:
34
  // Parse and update file_accesses top-level key.
35
  void updateFileAccesses(const JSON& file_accesses, const std::string& source);
36
37
  // Parse and update file_paths top-level key.
38
  void updateFilePaths(const JSON& file_paths, const std::string& source);
39
40
  // Parse and update file_paths_query top-level key.
41
  void updateFilePathsQuery(const JSON& file_paths_query,
42
                            const std::string& source);
43
44
  // Parse and update exclude_paths top-level key.
45
  void updateExcludePaths(const JSON& exclude_paths);
46
47
 private:
48
  /// The access map binds source to category.
49
  std::map<std::string, std::vector<std::string>> access_map_;
50
};
51
52
0
Status FilePathsConfigParserPlugin::setUp() {
53
0
  auto accesses_arr = data_.getArray();
54
0
  data_.add("file_accesses", accesses_arr);
55
0
  auto exclude_obj = data_.getObject();
56
0
  data_.add("exclude_paths", exclude_obj);
57
58
0
  access_map_.clear();
59
0
  return Status::success();
60
0
}
61
62
void FilePathsConfigParserPlugin::updateFileAccesses(
63
664
    const JSON& file_accesses, const std::string& source) {
64
664
  if (!file_accesses.doc().IsArray()) {
65
44
    return;
66
44
  }
67
68
1.23k
  for (const auto& category : file_accesses.doc().GetArray()) {
69
1.23k
    if (!category.IsString()) {
70
609
      continue;
71
609
    }
72
627
    std::string path = category.GetString();
73
627
    access_map_[source].push_back(path);
74
627
  }
75
76
620
  auto arr = data_.getArray();
77
620
  std::set<std::string> valid_categories;
78
17.5k
  for (const auto& access_source : access_map_) {
79
18.3k
    for (const auto& category : access_source.second) {
80
18.3k
      valid_categories.insert(category);
81
18.3k
    }
82
17.5k
  }
83
84
4.41k
  for (const auto& category : valid_categories) {
85
4.41k
    data_.pushCopy(category, arr);
86
4.41k
  }
87
620
  data_.add("file_accesses", arr);
88
620
}
89
90
void FilePathsConfigParserPlugin::updateFilePaths(const JSON& file_paths,
91
4.45k
                                                  const std::string& source) {
92
4.45k
  if (!file_paths.doc().IsObject()) {
93
10
    return;
94
10
  }
95
96
4.64k
  for (const auto& category : file_paths.doc().GetObject()) {
97
4.64k
    if (!category.value.IsArray() || !category.name.IsString()) {
98
231
      continue;
99
231
    }
100
101
4.41k
    std::string name = category.name.GetString();
102
274k
    for (const auto& path : category.value.GetArray()) {
103
274k
      if (!path.IsString()) {
104
261k
        continue;
105
261k
      }
106
107
13.1k
      std::string pattern = path.GetString();
108
13.1k
      if (pattern.empty()) {
109
145
        continue;
110
145
      }
111
112
12.9k
      replaceGlobWildcards(pattern);
113
12.9k
      Config::get().addFile(source, name, pattern);
114
12.9k
    }
115
4.41k
  }
116
4.44k
}
117
118
void FilePathsConfigParserPlugin::updateFilePathsQuery(
119
132
    const JSON& file_paths_query, const std::string& source) {
120
132
#ifdef OSQUERY_IS_FUZZING
121
132
  return;
122
#else
123
124
  if (!file_paths_query.doc().IsObject()) {
125
    return;
126
  }
127
128
  for (const auto& category : file_paths_query.doc().GetObject()) {
129
    if (!category.value.IsArray() || !category.name.IsString()) {
130
      continue;
131
    }
132
133
    std::string name = category.name.GetString();
134
    for (const auto& query : category.value.GetArray()) {
135
      if (!query.IsString()) {
136
        continue;
137
      }
138
139
      auto sql = SQL(query.GetString());
140
      if (!sql.ok()) {
141
        LOG(ERROR) << "Could not add file_paths using file_paths_query '"
142
                   << query.GetString() << "': " << sql.getMessageString();
143
      } else {
144
        for (const auto& row : sql.rows()) {
145
          auto pathIt = row.find("path");
146
          if (pathIt == row.end()) {
147
            LOG(ERROR) << "Cold not find non-empty 'path' column in the "
148
                          "results of file_paths_query '"
149
                       << query.GetString() << "'";
150
          } else {
151
            std::string path = pathIt->second;
152
            replaceGlobWildcards(path);
153
            Config::get().addFile(source, name, path);
154
          }
155
        }
156
      }
157
    }
158
  }
159
#endif
160
132
}
161
162
void FilePathsConfigParserPlugin::updateExcludePaths(
163
388
    const JSON& exclude_paths) {
164
388
  if (!exclude_paths.doc().IsObject()) {
165
84
    return;
166
84
  }
167
168
304
  auto obj = data_.getObject();
169
1.74k
  for (const auto& category : exclude_paths.doc().GetObject()) {
170
1.74k
    if (!category.value.IsArray() || !category.name.IsString()) {
171
414
      continue;
172
414
    }
173
174
1.33k
    auto arr = data_.getArray();
175
1.33k
    std::string category_string = category.name.GetString();
176
154k
    for (const auto& path : category.value.GetArray()) {
177
154k
      if (!path.IsString()) {
178
831
        continue;
179
831
      }
180
181
153k
      std::string path_string = path.GetString();
182
153k
      data_.pushCopy(path_string, arr);
183
153k
    }
184
1.33k
    data_.add(category_string, arr, obj);
185
1.33k
  }
186
187
  // Will attempt a merge so be careful that the key is initialized.
188
304
  if (!data_.doc().HasMember("exclude_paths")) {
189
1
    auto exclude_obj = data_.getObject();
190
1
    data_.add("exclude_paths", exclude_obj);
191
1
  }
192
304
  data_.mergeObject(data_.doc()["exclude_paths"], obj);
193
304
}
194
195
Status FilePathsConfigParserPlugin::update(const std::string& source,
196
64.6k
                                           const ParserConfig& config) {
197
64.6k
  Config::get().removeFiles(source);
198
64.6k
  access_map_.erase(source);
199
200
64.6k
  auto file_paths = config.find("file_paths");
201
64.6k
  auto file_paths_query = config.find("file_paths_query");
202
64.6k
  if (file_paths == config.end() && file_paths_query == config.end()) {
203
60.1k
    return Status::success();
204
60.1k
  }
205
206
4.48k
  if (file_paths != config.end()) {
207
4.45k
    updateFilePaths(file_paths->second, source);
208
4.45k
  }
209
210
4.48k
  if (file_paths_query != config.end()) {
211
132
    updateFilePathsQuery(file_paths_query->second, source);
212
132
  }
213
214
4.48k
  auto file_accesses = config.find("file_accesses");
215
4.48k
  if (file_accesses != config.end()) {
216
664
    updateFileAccesses(file_accesses->second, source);
217
664
  }
218
219
4.48k
  auto exclude_paths = config.find("exclude_paths");
220
4.48k
  if (exclude_paths != config.end()) {
221
388
    updateExcludePaths(exclude_paths->second);
222
388
  }
223
224
4.48k
  auto doc = JSON::newObject();
225
4.48k
  doc.copyFrom(data_.doc());
226
4.48k
  data_ = std::move(doc);
227
228
4.48k
  return Status::success();
229
64.6k
}
230
231
REGISTER_INTERNAL(FilePathsConfigParserPlugin, "config_parser", "file_paths");
232
} // namespace osquery