Skip to content

Commit 150b4c5

Browse files
committed
Release v1.0.1
Harden root-mode command execution and bump release metadata to 1.0.1.
1 parent 707e442 commit 150b4c5

6 files changed

Lines changed: 141 additions & 34 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ install(TARGETS mtop RUNTIME DESTINATION bin)
3434

3535
set(CPACK_GENERATOR "TGZ")
3636
set(CPACK_PACKAGE_NAME "mtop")
37-
set(CPACK_PACKAGE_VERSION "1.0.0")
37+
set(CPACK_PACKAGE_VERSION "1.0.1")
3838
include(CPack)
3939

4040
enable_testing()

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ Root-enhanced mode:
128128
sudo ./build/mtop
129129
```
130130

131+
Security note:
132+
133+
- `sudo` mode is optional and should only be used on systems you trust
134+
- do not run `mtop` as root on machines that are already compromised, jailbroken, tampered with, or otherwise in a questionable security state
135+
- root mode runs privileged telemetry collection and therefore inherits the security posture of the host system
136+
131137
UI preview mode:
132138

133139
```bash
@@ -236,7 +242,7 @@ Current workflow file:
236242
Recommended release flow:
237243

238244
1. Push normal commits and use the workflow artifacts to verify packaging.
239-
2. Create a version tag such as `v1.0.0`.
245+
2. Create a version tag such as `v1.0.1`.
240246
3. Push the tag.
241247
4. GitHub Actions will build and attach the package to the release.
242248

README.zh-CN.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,12 @@ root 增强模式:
128128
sudo ./build/mtop
129129
```
130130

131+
安全提示:
132+
133+
- `sudo` 模式是可选增强功能,只建议在你信任的系统上使用
134+
- 不要在已经被入侵、被破解、被篡改,或整体安全状态可疑的机器上以 root 方式运行 `mtop`
135+
- root 模式会触发特权遥测采集,因此它的安全边界直接受宿主系统当前安全状态影响
136+
131137
UI 预览模式:
132138

133139
```bash
@@ -236,7 +242,7 @@ packaging/homebrew/mtop.rb
236242
推荐发布流程:
237243

238244
1. 正常 push 代码,用 workflow artifacts 检查打包结果。
239-
2. 创建版本 tag,例如 `v1.0.0`
245+
2. 创建版本 tag,例如 `v1.0.1`
240246
3. push 这个 tag。
241247
4. GitHub Actions 会自动构建并把包挂到 Release 上。
242248

packaging/homebrew/mtop.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
class Mtop < Formula
22
desc "Apple Silicon terminal monitor for macOS"
33
homepage "https://github.com/lxrzlyr/mtop"
4-
url "https://github.com/lxrzlyr/mtop/releases/download/v1.0.0/mtop-1.0.0-source.tar.gz"
5-
sha256 "be34e89a628c15c5a73c985aa683c24e3aca6e63eabb55aacae6f6afce0324cf"
4+
url "https://github.com/lxrzlyr/mtop/releases/download/v1.0.1/mtop-1.0.1-source.tar.gz"
5+
sha256 "d73e5bc8bea54f360bb83994b04d5904aa7392237d7ab7fd9a4f658075853013"
66
license "GPL-3.0-or-later"
77

88
depends_on "cmake" => :build

src/main.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ void print_help() {
398398
}
399399

400400
void print_version() {
401-
std::printf("mtop 1.0.0\n");
401+
std::printf("mtop 1.0.1\n");
402402
}
403403

404404
std::string sort_mode_name(SortMode mode) {

src/platform/darwin_sampler.cpp

Lines changed: 123 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@
88
#include <mach/mach.h>
99
#include <mach/processor_info.h>
1010
#include <mach/vm_statistics.h>
11+
#include <fcntl.h>
1112
#include <pwd.h>
13+
#include <spawn.h>
1214
#include <sys/proc.h>
1315
#include <sys/sysctl.h>
1416
#include <sys/time.h>
17+
#include <sys/wait.h>
1518
#include <unistd.h>
1619

1720
#include <algorithm>
@@ -73,17 +76,113 @@ std::uint64_t current_uptime_seconds() {
7376
return now > boot_time.tv_sec ? static_cast<std::uint64_t>(now - boot_time.tv_sec) : 0;
7477
}
7578

76-
int gpu_core_count() {
77-
FILE* pipe = popen("system_profiler -json SPDisplaysDataType 2>/dev/null", "r");
78-
if (!pipe) {
79-
return 0;
79+
std::string run_command_capture(const std::vector<std::string>& args, bool discard_stderr) {
80+
if (args.empty()) {
81+
return "";
82+
}
83+
84+
int pipe_fds[2] = {-1, -1};
85+
if (pipe(pipe_fds) != 0) {
86+
return "";
8087
}
88+
89+
posix_spawn_file_actions_t actions;
90+
posix_spawn_file_actions_init(&actions);
91+
posix_spawn_file_actions_addclose(&actions, pipe_fds[0]);
92+
posix_spawn_file_actions_adddup2(&actions, pipe_fds[1], STDOUT_FILENO);
93+
posix_spawn_file_actions_addclose(&actions, pipe_fds[1]);
94+
95+
int devnull_fd = -1;
96+
if (discard_stderr) {
97+
devnull_fd = open("/dev/null", O_WRONLY);
98+
if (devnull_fd >= 0) {
99+
posix_spawn_file_actions_adddup2(&actions, devnull_fd, STDERR_FILENO);
100+
posix_spawn_file_actions_addclose(&actions, devnull_fd);
101+
}
102+
}
103+
104+
std::vector<char*> argv;
105+
argv.reserve(args.size() + 1);
106+
for (const auto& arg : args) {
107+
argv.push_back(const_cast<char*>(arg.c_str()));
108+
}
109+
argv.push_back(nullptr);
110+
111+
static char path_env[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin";
112+
static char lang_env[] = "LANG=C";
113+
static char lc_all_env[] = "LC_ALL=C";
114+
char* const safe_env[] = {path_env, lang_env, lc_all_env, nullptr};
115+
116+
pid_t pid = 0;
117+
const int spawn_rc = posix_spawn(&pid, args[0].c_str(), &actions, nullptr, argv.data(), safe_env);
118+
posix_spawn_file_actions_destroy(&actions);
119+
if (devnull_fd >= 0) close(devnull_fd);
120+
close(pipe_fds[1]);
121+
if (spawn_rc != 0) {
122+
close(pipe_fds[0]);
123+
return "";
124+
}
125+
81126
std::string output;
82127
std::array<char, 4096> buffer{};
83-
while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe) != nullptr) {
84-
output += buffer.data();
128+
ssize_t bytes = 0;
129+
while ((bytes = read(pipe_fds[0], buffer.data(), buffer.size())) > 0) {
130+
output.append(buffer.data(), static_cast<std::size_t>(bytes));
131+
}
132+
close(pipe_fds[0]);
133+
int status = 0;
134+
waitpid(pid, &status, 0);
135+
if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
136+
return "";
137+
}
138+
return output;
139+
}
140+
141+
bool run_command_quiet(const std::vector<std::string>& args) {
142+
if (args.empty()) {
143+
return false;
144+
}
145+
146+
posix_spawn_file_actions_t actions;
147+
posix_spawn_file_actions_init(&actions);
148+
const int devnull_fd = open("/dev/null", O_WRONLY);
149+
if (devnull_fd >= 0) {
150+
posix_spawn_file_actions_adddup2(&actions, devnull_fd, STDOUT_FILENO);
151+
posix_spawn_file_actions_adddup2(&actions, devnull_fd, STDERR_FILENO);
152+
posix_spawn_file_actions_addclose(&actions, devnull_fd);
153+
}
154+
155+
std::vector<char*> argv;
156+
argv.reserve(args.size() + 1);
157+
for (const auto& arg : args) {
158+
argv.push_back(const_cast<char*>(arg.c_str()));
159+
}
160+
argv.push_back(nullptr);
161+
162+
static char path_env[] = "PATH=/usr/bin:/bin:/usr/sbin:/sbin";
163+
static char lang_env[] = "LANG=C";
164+
static char lc_all_env[] = "LC_ALL=C";
165+
char* const safe_env[] = {path_env, lang_env, lc_all_env, nullptr};
166+
167+
pid_t pid = 0;
168+
const int spawn_rc = posix_spawn(&pid, args[0].c_str(), &actions, nullptr, argv.data(), safe_env);
169+
posix_spawn_file_actions_destroy(&actions);
170+
if (devnull_fd >= 0) close(devnull_fd);
171+
if (spawn_rc != 0) {
172+
return false;
173+
}
174+
int status = 0;
175+
waitpid(pid, &status, 0);
176+
return WIFEXITED(status) && WEXITSTATUS(status) == 0;
177+
}
178+
179+
int gpu_core_count() {
180+
const std::string output = run_command_capture(
181+
{"/usr/sbin/system_profiler", "-json", "SPDisplaysDataType"},
182+
true);
183+
if (output.empty()) {
184+
return 0;
85185
}
86-
pclose(pipe);
87186
const std::string token = "\"sppci_cores\" : \"";
88187
const std::size_t pos = output.find(token);
89188
if (pos == std::string::npos) {
@@ -181,20 +280,6 @@ std::string battery_description() {
181280
return result;
182281
}
183282

184-
std::string read_command_output(const std::string& command) {
185-
FILE* pipe = popen(command.c_str(), "r");
186-
if (!pipe) {
187-
return "";
188-
}
189-
std::string output;
190-
std::array<char, 4096> buffer{};
191-
while (fgets(buffer.data(), static_cast<int>(buffer.size()), pipe) != nullptr) {
192-
output += buffer.data();
193-
}
194-
pclose(pipe);
195-
return output;
196-
}
197-
198283
std::string username_for_uid(uid_t uid) {
199284
if (const passwd* entry = getpwuid(uid)) {
200285
return entry->pw_name;
@@ -618,11 +703,15 @@ class DarwinSampler final : public Sampler {
618703
int fd = mkstemp(plist_template);
619704
if (fd >= 0) {
620705
close(fd);
621-
std::string plist_command =
622-
"powermetrics --samplers cpu_power,gpu_power,thermal,ane_power -n 1 -i 1000 -f plist -o " +
623-
std::string(plist_template) + " </dev/null >/dev/null 2>&1";
624-
const int rc = std::system(plist_command.c_str());
625-
if (rc == 0) {
706+
const bool rc = run_command_quiet({
707+
"/usr/bin/powermetrics",
708+
"--samplers", "cpu_power,gpu_power,thermal,ane_power",
709+
"-n", "1",
710+
"-i", "1000",
711+
"-f", "plist",
712+
"-o", plist_template,
713+
});
714+
if (rc) {
626715
std::ifstream input(plist_template, std::ios::binary);
627716
std::vector<char> bytes((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
628717
if (!bytes.empty()) {
@@ -713,8 +802,14 @@ class DarwinSampler final : public Sampler {
713802
std::remove(plist_template);
714803
}
715804

716-
const std::string amp_text =
717-
read_command_output("powermetrics --samplers tasks,cpu_power --show-process-amp --show-process-ipc -n 1 -i 1000 </dev/null 2>/dev/null");
805+
const std::string amp_text = run_command_capture({
806+
"/usr/bin/powermetrics",
807+
"--samplers", "tasks,cpu_power",
808+
"--show-process-amp",
809+
"--show-process-ipc",
810+
"-n", "1",
811+
"-i", "1000",
812+
}, true);
718813
metrics.process_core_mix = parse_amp_core_mix(amp_text);
719814
return metrics;
720815
}

0 commit comments

Comments
 (0)