Flow-IPC 3.0.0
Flow-IPC project: Full implementation reference.
app.cpp
Go to the documentation of this file.
1/* Flow-IPC: Sessions
2 * Copyright 2023 Akamai Technologies, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
14 * CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License. */
17
18/// @file
19#include "ipc/session/app.hpp"
21#include "ipc/session/error.hpp"
23#include <flow/error/error.hpp>
24#include <cerrno>
25#include <fcntl.h>
26#include <sys/stat.h>
27
28namespace ipc::session
29{
30
31// Implementations.
32
33void ensure_resource_owner_is_app(flow::log::Logger* logger_ptr, const fs::path& path, const App& app,
34 Error_code* err_code)
35{
37 using boost::system::system_category;
38 using ::open;
39 // using ::O_PATH; // It's a macro apparently.
40 // using ::errno; // It's a macro apparently.
41
42 if (flow::error::exec_void_and_throw_on_error
43 ([&](Error_code* actual_err_code)
44 { ensure_resource_owner_is_app(logger_ptr, path, app, actual_err_code); },
45 err_code, "session::ensure_resource_owner_is_app(1)"))
46 {
47 return;
48 }
49 // else
50
51 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_SESSION);
52
53 /* We can either reuse the handle-based overload, or we can do stat() instead of fstat(). The former seems nicer;
54 * though we'll have to open a handle temporarily. I've seen boost.interprocess do it fairly casually for
55 * similar purposes, so why not. The O_PATH flag (which opens the resource just for this purpose -- not for
56 * I/O) is perfect for it, particularly since the resource might not be a file but a SHM pool, etc. */
57#ifndef FLOW_OS_LINUX
58 static_assert(false, "O_PATH is a Linux extension (also in some BSDs); when porting consider O_RDONLY at the cost "
59 "of requiring read permission -- see util::set_resource_permissions() -- or fs::status().");
60#endif
61
62 int native_handle = open(path.c_str(), O_PATH);
63 if (native_handle == -1)
64 {
65 *err_code = {errno, system_category()};
66 FLOW_LOG_WARNING("Tried to check ownership of resource at [" << path << "] but while obtaining info-only handle "
67 "encountered error [" << *err_code << "] [" << err_code->message() << "]; unable to check.");
68 return;
69 }
70 // else
71 Native_handle handle{native_handle};
72
73 // For nicer messaging add some more logging on error. A little code duplication, but it's OK.
74 ensure_resource_owner_is_app(logger_ptr, handle, app, err_code);
75 if (*err_code)
76 {
77 FLOW_LOG_WARNING("Check of ownership of resource at [" << path << "], upon successfully opening probe-only "
78 "descriptor/handle, resulted in error in checking or "
79 "unexpected ownership; see preceding WARNING referencing all other details.");
80 }
81
82 handle.close();
83} // ensure_resource_owner_is_app(1)
84
85void ensure_resource_owner_is_app(flow::log::Logger* logger_ptr, util::Native_handle handle, const App& app,
86 Error_code* err_code)
87{
88 using boost::system::system_category;
89 using ::fstat;
90 /* using Stats = struct ::stat; // This worked at coliru.com but not here; not sure why.
91 * // The crux of this whole thing is that struct ::stat() and ::stat()
92 * // have the same name. Blech. Just specify it explicitly below, C-style. */
93 // using ::errno; // It's a macro apparently.
94
95 if (flow::error::exec_void_and_throw_on_error
96 ([&](Error_code* actual_err_code)
97 { ensure_resource_owner_is_app(logger_ptr, handle, app, actual_err_code); },
98 err_code, "session::ensure_resource_owner_is_app(2)"))
99 {
100 return;
101 }
102 // else
103
104 assert((!handle.null()) && "Disallowed per contract.");
105
106 FLOW_LOG_SET_CONTEXT(logger_ptr, Log_component::S_SESSION);
107
108 // There's no Boost.filesystem or STL way to get UID/GID, that I know of (even via path); so use POSIX-y thing.
109 struct ::stat stats;
110 const auto rc = fstat(handle.m_native_handle, &stats);
111 if (rc == -1)
112 {
113 *err_code = {errno, system_category()};
114 FLOW_LOG_WARNING("Tried to check ownership via descriptor/handle [" << handle << "] but encountered "
115 "error [" << *err_code << "] [" << err_code->message() << "]; unable to check.");
116 }
117 else if ((stats.st_uid != app.m_user_id) || (stats.st_gid != app.m_group_id))
118 {
120 FLOW_LOG_WARNING("Checked ownership via descriptor/handle [" << handle << "]: owner UID:GID "
121 "[" << stats.st_uid << ':' << stats.st_gid << "] does not match App [" << app << "]; "
122 "emitting error [" << *err_code << "] [" << err_code->message() << "].");
123 }
124 else
125 {
126 err_code->clear();
127 }
128} // ensure_resource_owner_is_app(2)
129
130std::ostream& operator<<(std::ostream& os, const App& val)
131{
132 return os << '[' << val.m_name << "] "
133 "exec[" << val.m_exec_path << "] user[" << val.m_user_id << ':' << val.m_group_id << ']';
134}
135std::ostream& operator<<(std::ostream& os, const Client_app& val)
136{
137 return os << static_cast<const App&>(val);
138}
139
140std::ostream& operator<<(std::ostream& os, const Server_app& val)
141{
142 using boost::algorithm::join;
143
144 return os << static_cast<const App&>(val) << " allowed_cli_apps[" << join(val.m_allowed_client_apps, " ") << ']';
145}
146
147} // namespace ipc::session
@ S_RESOURCE_OWNER_UNEXPECTED
A resource in the file system (file, SHM pool, MQ, etc.) has or could have unexpected owner; ipc::ses...
Flow-IPC module providing the broad lifecycle and shared-resource organization – via the session conc...
Definition: app.cpp:29
std::ostream & operator<<(std::ostream &os, const App &val)
Prints string representation of the given App to the given ostream.
Definition: app.cpp:130
void ensure_resource_owner_is_app(flow::log::Logger *logger_ptr, const fs::path &path, const App &app, Error_code *err_code)
Utility, used internally but exposed in public API in case it is of general use, that checks that the...
Definition: app.cpp:33
util::Native_handle Native_handle
Convenience alias for the commonly used type util::Native_handle.
flow::Error_code Error_code
Short-hand for flow::Error_code which is very common.
Definition: common.hpp:313
A description of an application in this ipc::session inter-process communication universe.
Definition: app.hpp:88
util::group_id_t m_group_id
The application must run as this group ID (GID). Files and other shared resources shall have this own...
Definition: app.hpp:206
std::string m_name
Brief application name, readable to humans and unique across all other applications' names; used both...
Definition: app.hpp:179
util::user_id_t m_user_id
The application must run as this user ID (UID). Files and other shared resources shall have this owne...
Definition: app.hpp:203
fs::path m_exec_path
Absolute, lexically normalized canonical path to the executable entity (which is not a directory),...
Definition: app.hpp:200
An App that is used as a client in at least one client-server IPC split.
Definition: app.hpp:220
An App that is used as a server in at least one client-server IPC split.
Definition: app.hpp:241
Client_app_set m_allowed_client_apps
A given Client_app (as identified by its distinct App::m_name) may request to establish an IPC sessio...
Definition: app.hpp:257
A monolayer-thin wrapper around a native handle, a/k/a descriptor a/k/a FD.
constexpr bool null() const noexcept
Returns true if and only if m_native_handle equals S_NULL_HANDLE.
handle_t m_native_handle
The native handle (possibly equal to S_NULL_HANDLE), the exact payload of this Native_handle.