blob: 68e80237dd88da9b24e7e764a81457fc18b3a21f [file] [log] [blame]
// Copyright 2018 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef RUNTIME_PROBE_PROBE_FUNCTION_H_
#define RUNTIME_PROBE_PROBE_FUNCTION_H_
#include <array>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
#include <base/json/json_writer.h>
#include <base/values.h>
#include <base/strings/string_util.h>
namespace runtime_probe {
// Creates a probe function. This is a syntax suger for |FromKwargsValue|.
template <typename T>
std::unique_ptr<T> CreateProbeFunction(
const base::Value::Dict& dict_value = {}) {
return T::template FromKwargsValue<T>(dict_value);
}
class ProbeFunction {
// ProbeFunction is the base class for all probe functions. A derived
// class should implement required virtual functions and contain some static
// members: |function_name|, FromKwargsValue().
//
// FromKwargsValue is the main point to create a probe function instance. It
// takes a dictionary value in type base::Value as arguments and returns a
// pointer to the instance of the probe function.
//
// Formally, a probe function will be represented as following structure::
// {
// <function_name:string>: <args:ArgsType>
// }
//
// where the top layer dictionary should have one and only one key. For
// example::
// {
// "sysfs": {
// "dir_path": "/sys/class/cool/device/dev*",
// "keys": ["key_1", "key_2"],
// "optional_keys": ["opt_key_1"]
// }
// }
public:
using DataType = base::Value::List;
// Interface to parse a function argument. See RegisterArgumentParser().
class ArgumentParser {
public:
ArgumentParser() = default;
virtual ~ArgumentParser() = default;
// Implement this to parse the argument from a value. |value| set to
// |std::nullopt| means that the field is not found in the argument list.
// Returns true if parse succeeds. Set |err| to some error messages which
// will be logged with the function name and the field name (|err| will be
// ignored if returning true).
virtual bool Parse(const std::optional<base::Value>& value,
std::string& err) = 0;
};
// Returns the name of the probe function. The returned value should always
// identical to the static member |function_name| of the derived class.
//
// A common implementation can be declared by macro NAME_PROBE_FUNCTION(name)
// below.
virtual const std::string& GetFunctionName() const = 0;
// Converts |dv| with function name as key to ProbeFunction. Returns nullptr
// on failure.
static std::unique_ptr<ProbeFunction> FromValue(const base::Value& dv);
// Creates a probe function of type |T| with arguments. Returns nullptr if
// arguments cannot be parsed.
template <typename T>
static std::unique_ptr<T> FromKwargsValue(
const base::Value::Dict& dict_value) {
std::unique_ptr<T> fun{new T()};
if (fun->ParseArguments(dict_value)) {
return fun;
}
return nullptr;
}
ProbeFunction(const ProbeFunction&) = delete;
ProbeFunction& operator=(const ProbeFunction&) = delete;
virtual ~ProbeFunction();
// Evaluates this probe function. Returns a list of base::Value. For the probe
// function that requests sandboxing, see |PrivilegedProbeFunction|.
virtual DataType Eval() const { return EvalImpl(); }
// This is for helper to evaluate the probe function. Helper is designed for
// portion that need extended sandbox. See |PrivilegedProbeFunction| for more
// detials.
//
// Output will be an integer and the interpretation of the integer on
// purposely leaves to the caller because it might execute other binary
// in sandbox environment and we might want to preserve the exit code.
virtual int EvalInHelper(std::string* output) const;
// Registers an ArgumentParser. The callers need to guarantee the lifecycle of
// ArgumentParser object. It should be alive when calling
// |ParseArguments()|.
void RegisterArgumentParser(const std::string field_name,
ArgumentParser* parser);
using FactoryFunctionType =
std::function<std::unique_ptr<ProbeFunction>(const base::Value::Dict&)>;
using RegisteredFunctionTableType =
std::map<std::string_view, FactoryFunctionType>;
// Mapping from |function_name| to FromKwargsValue() of each derived classes.
static RegisteredFunctionTableType registered_functions_;
protected:
ProbeFunction();
// Implement this method to provide the probing. The output should be a list
// of base::Value.
virtual DataType EvalImpl() const = 0;
// Implement this to verify the parsed arguments or do other set up which
// based on arguments (e.g. create other probe functions with the arguments).
// It is called after all ArgumentParser are executed. Returning false makes
// the |ParseArguments()| fail.
virtual bool PostParseArguments() { return true; }
// Gets the arguments. It is the raw arguments passed to this function.
const base::Value::Dict& arguments() const { return arguments_; }
private:
// Parses the probe function arguments. Returns false when error.
bool ParseArguments(const base::Value::Dict& arguments);
// A map of argument field names to argument parsers.
std::map<std::string, ArgumentParser*> argument_parsers_;
// The raw arguments.
base::Value::Dict arguments_;
};
class PrivilegedProbeFunction : public ProbeFunction {
// |PrivilegedProbeFunction| run in the sandbox with pre-defined permissions.
// This is for all the operations which request special permission like sysfs
// access. |PrivilegedProbeFunction| will be initialized with same json
// statement in the helper process, which invokes |EvalImpl()|. Since
// execution of |PrivilegedProbeFunction::EvalImpl()| implies a different
// sandbox, it is encouraged to keep work that doesn't need a privilege in
// |PostHelperEvalImpl()|.
//
// For each |PrivilegedProbeFunction|, please modify `sandbox/args.json` and
// `sandbox/${ARCH}/${function_name}-seccomp.policy`.
using ProbeFunction::ProbeFunction;
public:
// ProbeFunction overrides.
DataType Eval() const final;
int EvalInHelper(std::string* output) const final;
protected:
// Serializes this probe function and passes it to helper. The output of the
// helper will store in |result|. Returns true if success on executing helper.
bool InvokeHelper(std::string* result) const;
// Serializes this probe function and passes it to helper. Helper function
// for InvokeHelper() where the output is known in advanced in JSON format.
// The transform of JSON will be automatically applied. Returns std::nullopt
// on failure.
std::optional<base::Value> InvokeHelperToJSON() const;
private:
// This method is called after |EvalImpl()| finished. The |result| is the
// value returned by |EvalImpl()|. Because |EvalImpl()| is executed in helper,
// this method is for those operations that cannot or don't want to be
// performed in helper, for example dbus call. This method can do some extra
// logic out of helper and modify the |result|. See b/185292404 for the
// discussion about this two steps EvalImpl.
virtual void PostHelperEvalImpl(DataType* result) const {}
};
// Tells if T is a subclass of ProbeFunction.
template <typename T>
inline constexpr auto is_probe_function_v = std::is_base_of_v<ProbeFunction, T>;
// Represents a list of ProbeFunction.
template <typename... Ts>
class ProbeFunctions {
static_assert((is_probe_function_v<Ts> && ...),
"Ts must be a subclass of ProbeFunction");
public:
// Gets an array of all function names.
static constexpr std::array<const char*, sizeof...(Ts)> GetFunctionNames() {
return {Ts::function_name...};
}
// Constructs a table mapping function name to its factory method.
static ProbeFunction::RegisteredFunctionTableType
ConstructRegisteredFunctionTable() {
return {{Ts::function_name, CreateProbeFunction<Ts>}...};
}
};
#define NAME_PROBE_FUNCTION(name) \
const std::string& GetFunctionName() const override { \
static const std::string instance(function_name); \
return instance; \
} \
static constexpr auto function_name = name
} // namespace runtime_probe
#endif // RUNTIME_PROBE_PROBE_FUNCTION_H_