-
-
Notifications
You must be signed in to change notification settings - Fork 449
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FlexMeter: Add FlexMeter functionality #1571
base: main
Are you sure you want to change the base?
Conversation
Please have a very close look at your implementation again as I noticed several trivial buffer overflows in the file iteration/handling code. Furthermore I'd like to point you to our styleguide which gives additional guidance on how the code should be set up. Also when integrating this meter, we have to take care of privilege escalations when running the specified commands. This is in particular true when running htop as root via sudo, when the home directory is still set to the logged-in user's HOME directory. In that situation a command in |
Just wondering, why was this meter called FlexMeter? Was it a random name you thought of? Also, I agree with @BenBE on the security issue here. The shell script to launch should have its owner same as the EUID or else htop should refuse to execute it. |
@BenBE I looking in codding stile which I might not follow strictly , and thanks for remark on overflow issue I am aware of it but totally forget to fix it @Explorer09 FlexMeter was chosen because you can select name of the Meter created from configuration file, I thought it was good. I was looking for easy and simple way to extend my htop with some stats, which would require development of bunch of specific meters for simple one line shell for example. Regarding PCPDynamicMeter - I was looking for something simpler. This was my idea. I was looking to report some custom stuff from my system like peripherals battery status or UPS work temperature.
I will fix security issue too as far it is possible. |
The buffer overflow was just one thing in the implementation. What initially tipped me off was the extensive use of static buffers all over the place. Overall,
I think naming-wise I'm fine with both: FlexMeter or DynamicMeter. Depending on how much infrastructure can be shared with the PCP implementation, calling it DynamicMeter might be an option; but that might be a source of confusion.
AFAICS the current implementation only implements text mode? Maybe we should limit it to that too; thinking re #1387 …
Both the buffer overflows (CWE-787, CWE-121, and CWE-122) and the privilege escalation (CWE-250, CWE-265, CWE-266,, CWE-269, CWE-270, and CWE-273,) are all security issues; the privilege escalation is just the more obvious architectural one, which needs some more thorough thoughts to mitigate. A good rule of thumb is to assume that every bug your code has will wipe your system. Now write your code like (if) you value your data … |
275830e
to
595b1ee
Compare
I have been working to make FlexMeter more compliant with all comments above. Used xStrdup in place of previous implementation. I followed If we agree lest stick with FlexMeter, since AFAIK it does not share functionality and implementation with PCP and might be confusing.
Yes it implements only text mode at the moment. I kept type in implementation since I thought it might be cool to implements and others. So far it look like it is not needed.
Most of this are addressed. I need to handle privileges for files located in |
FlexMeter.c
Outdated
// path to home folder 1 for zero 1 for slash | ||
char * home = (char * ) xCalloc(1,(strlen(homedir) + strlen(FLEX_CFG_FOLDER) + 2)); | ||
|
||
sprintf(home,"%s/%s",homedir,FLEX_CFG_FOLDER); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use xSnprintf
. sprintf
is insecure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I forgot to mention this: htop complies with XDG Base Directory specification and that means we fetch user's config directory with the variable XDG_CONFIG_HOME
. Specifically the flex meter directories should go in $XDG_CONFIG_HOME/htop/FlexMeter
. Don't hard-code the .config
directory part and don't use $HOME/.config/htop/FlexMeter
. Look in the Settings.c file in htop source code to see how we fetch the XDG_CONFIG_HOME
variable and use the fallback default if it's unset.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will check that, thanks
Before I review more about the code style and quality of the meter, I have some general comments:
Honestly, I don't like the current design of the template file format. It mixes static data and executable code, and that's a bad idea. Also, it does not allow me to indicate the shell to use (i.e. it contains no shebang). I know you are using In other words, I wish the FlexMeter template files be shell scripts themselves, with appropriate shebang and execute permission (mode) bits. This would help the security in the long run. (The last thing I wish to see is a systemd-style, INI-like format for managing executable sub-processes. I was referring to this "comparison of init systems" page.)
AFAIK, the |
P.S. I have a rough logic of determining whether the user can or has intent to run the commands. But it needs execute permission on the template files themselves:
I'm not sure if it's worth it to implements a "drop privilege" mechanism or switch back to UID on this. But if there is a way to drop privilege, then the logic of checking whether the commands should run with the UID would be same as when checking with the EUID. |
@Explorer09 What do you think about handling group and user sticky bits on the files? How to handle symlinks? |
Note that my proposed logic only checks for execute permission of the meter (template) files themselves, and not on the executability of the commands. My imagined case is when the meter command was
Update: I've missed one thing about symlinks. We should only follow the symlink if it's created or owned by the user (here I mean EUID). It's unsafe to follow a link created by someone else. If a symlink is created by the user named "alice" but she runs htop as root, then the meter script should drop privileges and run as if EUID = Alice's UID. |
@Explorer09 Thanks for linking that old issue. @bogdanovs What do you think of the ideas/protocol mentioned in that issue #526? |
This issue is similar to my proposal but more complex to be used in first glance. In next iteration of FlexMeter could be implemented different than TEXT_MODE, but this will require scripting on background to provide more specific data. This is not bad but more demanding from user. FlexMeter is some how simple , not so tight to anything else. You can show whatever data you want. Issue described is more restrictive and might be harder for most average used to jump in directly. In the proposal is mentioned just 4 additional meters which is really limiting. Currently with this implementation I have between 6 an 8 all the time. If it is custom , let it be custom. I dont like part with open pipe all the time and somebody just feeding it. |
|
595b1ee
to
2a39429
Compare
Correct. If the mode is 600, the user can still read and edit the file but would work as a sign that "this meter wasn't ready / it's a draft".
I could make it work like a standalone script and use watch(1) command to monitor the same thing. |
Yes it make sens to use But I still think |
Your popen(3) call will launch the command under the |
Correct Well it should not affect FlexMeter configuration if there is other shebang, I can generally ignore it while loading and care only for whatever keys words I am supporting. |
f56e58e
to
b9e5907
Compare
Summary on latest update :
|
I would choose to run the meter command only when the meter is added. Not any earlier. Think of when the command is I will have comments about the permission check code later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly split the unrelated changes into a separate commit.
Also, the types within FlexMeter.c
don't follow the usual naming convention?
Any reason for the hard limit on supported flex meters?
struct passwd* pw = getpwuid(getuid()); | ||
const char* homedir = pw->pw_dir; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the comment by @Explorer09 regarding the XDG_HOME_DIR
variable and fallbacks …
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is current XDG_CONFIG_HOME
and fallbacks enough ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getpwuid
has some bad side effects (calls to libpam and potentially causes network traffic; thus can potentially hang). Furthermore, when building htop with --enable-static
this may cause issues, thus call sites should be minimized.
Not to mention the call can fail and is lacking error checks.
Please compare with Settings_new
in Settings.c
. Possibly, we should refactor this code block to be a separate function. @Explorer09?
@BenBE Is it mandatory to user |
If semantics are pure truth values: yes. If these functions provide full error reasons as their return value, If using |
b9e5907
to
12a91fd
Compare
FYI: Just pushed latest version which is without some comments which are already under some kind of discussion. |
FlexMeter.c
Outdated
meter_list[meters_count].name = xStrdup(dir->d_name); | ||
xAsprintf(&path, "%s/%s", home, dir->d_name); | ||
|
||
if (access(path, R_OK | W_OK | X_OK) == 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need write access here? RX should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no we dont need write access , but this match what we discuss for 700
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it'd be best to allow both 0500 and 0700 so the user can disable write access as that is not needed for the feature to work. The check should be for the minimum we require, with 0700 being the maximum we allow (technically even 0755 would still be fine).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Sorry for the last comment about 0400 as I misread the code. I deleted that comment.)
By the way, do we have a better API than access(2) here? access(2) is vulnerable to TOCTOU and I wish to avoid it when possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An option may be to use open(2)
with O_EXCL
and fstat(2)
the fd
, before passing it on to fdopen(3)
in load_config
, which would need to receive the fd
(or a FILE*
, if we did the fdopen(3)
in the caller).
There is even the possibility to use faccessat(2)
with AT_EMPTY_FILE
to emulate access(2)
using an already open fd
, but this is Linux-specific (read: non-portable) and requires a recent (>=5.8) kernel to work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about replacing access(2)
with stat(2)
like for folder ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BenBE @bogdanovs
How about doing it in the same way that we check for htoprc
?
open(2)
the file with the option (O_RDONLY | O_NOFOLLOW
); after it succeeds, fstat(2)
the file descriptor and check its execute bit (sb.st_mode & S_IXUSR) != 0
; after both of these are done, fdopen(3)
.
The stat
structure should give us enough information to check not only for modes but also owner ID and group ID. If you are able, you may try implementing the whole logic I made in a previous comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BenBE @bogdanovs How about doing it in the same way that we check for
htoprc
?open(2)
the file with the option (O_RDONLY | O_NOFOLLOW
); after it succeeds,fstat(2)
the file descriptor and check its execute bit(sb.st_mode & S_IXUSR) != 0
; after both of these are done,fdopen(3)
.The
stat
structure should give us enough information to check not only for modes but also owner ID and group ID. If you are able, you may try implementing the whole logic I made in a previous comment.
if we agree on stat
approach I will implement it that way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Explorer09 What's the difference in your suggestion compared to mine? Mine was mostly focused on the actual files, but would work similar for the directory too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@BenBE Probably no difference, except that the O_EXCL
flag you mentioned won't do what you want. (O_EXCL
is for creating the file only - it forces open(2)
to error out if a file with the same name exists).
FlexMeter_class[i].name = (const char*) xStrdup(meter_list[i].name); | ||
FlexMeter_class[i].uiName = (const char*) xStrdup(meter_list[i].uiName); | ||
FlexMeter_class[i].caption = (const char*) xStrdup(meter_list[i].caption); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given the unrestricted nature of how the uiName
and caption
are set, a FlexMeter could just configure to use the same strings as another (built-in) meter and thus impersonate another (e.g. built-in) meter. This is not a security issue directly, but more a point for discussion, if we want to allow the user to create FlexMeters that are indistinguishable from built-in meters.
12a91fd
to
71eed8e
Compare
71eed8e
to
55f08c2
Compare
FlexMeter.c
Outdated
if (home != NULL) { | ||
free(home); | ||
home = NULL; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can assume free(NULL)
will do nothing. This behavior is required by ANSI C standard. (Operating systems that do not have this behavior are too old for htop to support.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be precise: A libc where free(NULL);
is anything but a no-op does not comply to the ISO C99 standard (section 7.20.3.2) and is thus outside the scope of what we support.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to confirm which approach is preferred here with if (ptr != NULL)
of without ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without …
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My fault , initially I understood that this check is required.
I will remove them
I think this format will encourage users to have more complex multi line commands which will cause issues. I would like to keep current format: name=Meter name
type=text
caption=Caption
command=<one line command or call script> |
FlexMeter provides functionality which will allow users to make custom meters without need of rebuilding every time htop binary and adding source to the project. It can be used to print some device status, free disk space CPU or other specific temeraturer, fan RPM and many more. Everything that can be fetched from linux shell with one line result can be printer. For fething information can be used anything from shell, python, precompiled binary or simply reading file located somewhere in file system. New meter will appear uppon restart of htop in list with available meters. Configuration folder location where metes should be placed: - /home/$USER/.config/htop/FlexMeter/ On start folder will be created if does not exist, together with template file .Template in same folder. Note: Files starting with '.' (.Template for examlpe) are ignored Meter Example: File name : Template name=<NAME SHOWN IN AvailableMeters> command=<COMMAND WHICH WILL BE EXECUTED> type=<METER TYPE FOR NO ONLY "TEXT_METER"> caption="CAPTION TEXT SHOWN IN THE BEGGINING OF THE METER" According to this implementation 30 Flex meter can be added Currently they have hardcoded limit of 30 meter in addition to all that already exist. Signed-off-by: Stoyan Bogdanov <[email protected]>
Signed-off-by: Stoyan Bogdanov <[email protected]>
55f08c2
to
c337785
Compare
In last update
|
What issues? It was intentional for me to support multiline commands, because there's no reason not to. Also, how was it any different if I can squeeze multiple commands already into one line? |
The only way I think supporting multi-line commands makes sense is if we were to run the config files as a shell script (in which case the shabang+commented-out settings make sense). But this opens up a whole other can of worms. In the end, either way will handle arbitrary scripts run with some set of privileges (TBD), where you will be able to spawn shells and sub-processes. TBH; there's no sane way to make this anywhere "safe". But, going the "shell script route" encourages to have more complex behaviours packaged in such meters, which is IMO nothing we should encourage, even though it's possible (and there's no real way in stopping the user from doing). Thus I somewhat prefer the single-line approach as it tends to make the config files simpler and cause the nitty-gritty details out into external scripts that can be written in whatever language fits better than a basic shell script used to bootstrap things. |
There is nothing going simpler when you limit the command to one line. The real thing that could make the script simpler is limiting the total length of the commands htop could load per file. For example 4 KiB. Also we can limit the interpreter shebang to The point of putting multiple commands in a meter file is that htop can keep the commands in memory so that any modification to the meter file after htop started would not affect that htop instance of starting commands. If the commands are launched through another script (be it shell script or Perl or Python), then they lose this advantage. |
Additional comments...
The reality is that we are running it as a shell script due to how popen(3) command works. If we have been forking and then using exec(3) family of functions, without As I mentioned in a comment before, the last thing I wished to see is an executable script file modeled like an INI that looked harmless in a glance. (Many INI-like files in Microsoft Windows can contain executable code, mind you, the most notable of which are driver installation
Acknowledged. Although my vision is to makes things simple and utilize Unix mechanism of user privileges whenever possible. That is to say, (1) don't be afraid of running arbitrary scripts with elevated privileges if there are enough permission bits indicating the users have intent to do so, and (2) we can restrict the execution of scripts that are owned by someone else, except when the owner is By the way, I've changed some mind about the SUID bit on meter files. I think we can allow them. Even though that could result in an elevation of privilege, ultimately it's not the fault of us or the user who execute it, it is the file owner who is responsible. |
I'm afraid of neither and the point for discussion basically boils down to whether we want soem INI like format which emphasizes/prioritizes single-line commands or if we would prefer to go the route of shell scripts, with the meta-information placed in a header at the top of the file. I'm fine with either way, but somewhat prefer the INI format as it makes parsing out the information we need to know for the meter much simpler. In the end, you can't run code without somewhere placing the code you want to run.
No objection here.
I just tend to be very careful whenever there is some "arbitrary code execution" on a code path. Occupational hazard …
ACK. But coming from an IT sec perspective, I'd prefer to look at it that way: How much do we have to allow for things to work properly. Ideally, scripts would run with no more than the privileges of the owner, but also no higher privileges than the user who executes them. Which brings us to the next point:
We could use the SUID bit in the sense it is used on regular executables: Run as the owner of that file. That way, by default meters would be executed as the user running htop, except when a meter is SUID, in which case it would inherit that user's privileges when executing the meter. This would leave us with 10 cases basically:
Alternatively, you could ignore SUID if the meter was not owned by root, which gives the following cases:
|
{ | ||
case 'n': | ||
if (String_startsWith(line, "name=")) { | ||
xAsprintf(&meter->uiName, "Flex: %s", line + 5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Memory leak, if multiple lines with name=
are provided.
Similar below.
Have a look at free_and_xStrdup
for the cases below.
For the xAsprintf
call site calling free
on meter->uiName
should suffice.
bool ret = false; | ||
FILE* fp = fopen(file, "r"); | ||
|
||
if (fp != NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note, that fclose(NULL);
is undefined behaviour, usually characterized to crash your program. ;-)
if (fp != NULL) { | |
if (!fp) { | |
return false; | |
} | |
Doing it that way around also allows to lower the indentation level.
} | ||
} | ||
free(buff); | ||
buff = NULL; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not strictly necessary. should best be removed.
For stack variables, calling free
suffices. Only when the variable or its value is exposed to other scopes it's necessary to explicitly set the value to NULL
.
struct passwd* pw = getpwuid(getuid()); | ||
const char* homedir = pw->pw_dir; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getpwuid
has some bad side effects (calls to libpam and potentially causes network traffic; thus can potentially hang). Furthermore, when building htop with --enable-static
this may cause issues, thus call sites should be minimized.
Not to mention the call can fail and is lacking error checks.
Please compare with Settings_new
in Settings.c
. Possibly, we should refactor this code block to be a separate function. @Explorer09?
FlexMeter_class[i].name = (const char*) xStrdup(meter_list[i].name); | ||
FlexMeter_class[i].uiName = (const char*) xStrdup(meter_list[i].uiName); | ||
FlexMeter_class[i].caption = (const char*) xStrdup(meter_list[i].caption); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For caption=
all* >=32
is fine. With just 26<=c && c<=126
you cause two issues:
- You allow for inserting ECMA48 escape sequences (which can rebind keys -> security issue)
- You break Unicode support for things like
caption=運勢
(when executingcommand=LANG=ja_JA fortune
)
*Unicode filtering has its whole other can of worms … But that would apply to the output of command too …
@@ -43,7 +44,6 @@ in the source distribution for its full text. | |||
#include "dragonflybsd/DragonFlyBSDProcessTable.h" | |||
#include "generic/fdstat_sysctl.h" | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second blank line after includes.
@@ -47,7 +48,6 @@ in the source distribution for its full text. | |||
#include "openbsd/OpenBSDMachine.h" | |||
#include "openbsd/OpenBSDProcess.h" | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second blank line after includes.
@@ -55,7 +56,6 @@ in the source distribution for its full text. | |||
#include "zfs/ZfsArcStats.h" | |||
#include "zfs/ZfsCompressedArcMeter.h" | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second blank line after includes.
@@ -43,7 +44,6 @@ in the source distribution for its full text. | |||
#include "zfs/ZfsArcMeter.h" | |||
#include "zfs/ZfsCompressedArcMeter.h" | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second blank line after includes.
@@ -27,7 +28,6 @@ in the source distribution for its full text. | |||
#include "TasksMeter.h" | |||
#include "UptimeMeter.h" | |||
|
|||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second blank line after includes.
The INI-like format is foreign to a Unix like environment when it comes to shell commands. That's the primary reason I suggest against that. Really. You need to handle the quoting and character escape mechanisms, and perhaps also distinguishing
Just for another information, I know that an INI-like format could work if we are executing a program directly without a shell. The closest example we can take as reference is the desktop entry format (
I was about to think of all possible cases before you presented this list to me. Thanks. For case 2., this is a case I also wish to avoid, and I should mention that htop should also ignore the "others execute" bit in this situation. How to combine these points into a privilege evaluation logic/code that we can make it into htop, by the way? |
If I didn't mess up … Also, I'm deliberately ignoring any bits apart from the owner bits and SUID. |
FlexMeter provides functionality which will allow
users to make custom meters without need of rebuilding every time htop binary and adding source to the project. It can be used to print some device status, free disk space CPU or other specific temeraturer, fan RPM and many more. Everything that can be fetched from linux shell
with one line result can be printer. For fething information can be used anything from shell, python, precompiled binary or simply reading file located somewhere in file system.
New meter will appear uppon restart of htop in list with available meters.
Configuration folder location where metes should be placed:
Note: Files starting with '.' (.Template for examlpe) are ignored
Meter Example:
File name : Template
name=
command=
type=<METER TYPE FOR NO ONLY "TEXT_METER">
caption="CAPTION TEXT SHOWN IN THE BEGGINING OF THE METER"
According to this implementation 30 Flex meter can be added Currently they have hardcoded limit of 30 meter in addition to all that already exist.
I am using this functionality for about an years maybe, so far had no issues. It might not be most optimal implementation by try to follow project stile while developed it. I am open for suggestion to improvements.