0

I am writing a C program to change the screen brightness as xbacklight does not work in my circumstance. The solution should be native C (no system() function), because the program should be executable as a normal user via setuid. Calling external shell commands or scripts causes the kernel to ignore this bit.

Reading the proc file that controls the brightness works fine, but writing to it using C produces no result, even if I run the program as root. The fprintf call returns -130, indicating an error. As a sanity check, I included a working solution using system() as a comment.

[...]
const char* brightness = "/sys/class/backlight/intel_backlight/brightness";
f = fopen(brightness, (!strncmp(argv[1], "get", 3)) ? "r" : "rw");
[...]

int get_brightness() {
  int buff;
  fscanf(f, "%d", &buff);
  return buff;
}

int set(int i) {
  i = MAX(0, MIN(255, i));
  fprintf(f, "%d", i);
  printf("%d", i);
  //char *cmd = (char*) malloc(59 *sizeof(char));
  //snprintf(cmd, 59, "echo %d > %s", i, brightness);
  //system(cmd);
  //free(cmd);
}

Sermak
  • 3
  • 1
  • Why are you using rw mode? – Shawn Jul 10 '19 at 22:45
  • And what is `errno` after it fails (your code doesn't check for `fprintf()` failure...)? Use `sterror()` or `perror()` to get a human readable error. – Shawn Jul 10 '19 at 22:48
  • Does `fprintf(f, "%d\n", i);` work any better? – Mark Plotnick Jul 10 '19 at 23:59
  • @MarkPlotnick The values written in `/sys` and `/proc` filelets don't have to be terminated by a newline. –  Jul 11 '19 at 00:03
  • To write to `/proc`, your process must run with `root` privileges. Have you confirmed that `# echo 5 > /sys/class/backlight/intel_backlight/brightness` actually changes the brightness? Some distro implementations do not actually accept changes to `brightness`. You should read `/sys/class/backlight/intel_backlight/max_brightness` to set the upper limit of acceptable values. – David C. Rankin Jul 11 '19 at 00:35
  • I have looked a bit further at this. I do the same thing but with a shell script for controlling brightness in KDE3 with the Nvidia driver through `.../acpi_video0/brightness`. It seems there are certain rules you must follow (that the shell is already written to follow) when you attempt to write to `sysfs`. Specifically from `kernel.org` itself, [Rules on how to access information in sysfs](https://www.kernel.org/doc/html/latest/admin-guide/sysfs-rules.html). The biggest issues is you cannot use `/sys/class/backlight/intel_backlight/brightness` (it is a `link`) – David C. Rankin Jul 11 '19 at 02:31

1 Answers1

1
f = fopen(brightness, (!strncmp(argv[1], "get", 3)) ? "r" : "rw");

"rw" is not a valid argument for the mode argument of fopen(3). To open a file in read/write mode with fopen(3), you should use "r+".

Using "rw" is undefined behaviour -- in Linux/glibc it will be treated just as a single "r", the file will be opened in read-only mode, and the printf -> write will fail.

Generally, it's not a very smart idea to use buffered i/o with such tiny files. You should look into dprintf(3), if you need to write formatted data to a file descriptor.

Also, I would just use a list of fixed values in a setuid program, instead of having to validate arguments, and take care that the validating code don't become itself a liability, etc.

  • I believe this answer misses the mark, see [Rules on how to access information in sysfs](https://www.kernel.org/doc/html/latest/admin-guide/sysfs-rules.html) (not that the issue you identified is wrong...) – David C. Rankin Jul 11 '19 at 02:32
  • Really, did you miss *"all elements of a devpath must be real directories. Symlinks pointing to /sys/devices must always be resolved to their real target and the target path must be used to access the device. That way the devpath to the device matches the devpath of the kernel used at event time."*? – David C. Rankin Jul 11 '19 at 02:57
  • I do, and a simple `strace` (or proper validation after `fclose`) after an attempted write to a path on `sysfs` that contains a symlink with result in `EINVAL` (invalid access) of the path containing a symlink. If you truly think that is just a recommendation, you can test, and then go tell the folks at `kernel.org`, that they got their documentation wrong. ? – David C. Rankin Jul 11 '19 at 03:05
  • Don't get me wrong, I'm not criticizing you. This is very non-apparent behavior from a normal file read/write standpoint. Normally, a link is the file for all practical purposes, accept in this case. – David C. Rankin Jul 11 '19 at 03:09
  • strace is tracing system calls like open(2) or write(2), not library functions like fprintf(3) or fclose(3). And you never write to a *path*, but you open(2) a path (which, if a symlink, will be resolved by the kernel), and then write to the file descriptors returned by open(2). And `EINVAL` is **not** invalid access, but "invalid argument". –  Jul 11 '19 at 03:09
  • You can adapt and test: [C - Write sysfs brightness](http://susepaste.org/71797495) - expires: Wed Aug 7 22:10:39 CDT 2019 – David C. Rankin Jul 11 '19 at 03:12
  • @David have you straced `echo 42 > class/backlight/acpi_video0/brightness`? Does it do any of what you suggest? – n. m. could be an AI Jul 11 '19 at 03:25
  • @n.m. Range here is `0-20` and `echo` any value in between appropriately sets the backlight (as root). The same applies to the posted code with the proper device ID. However, if you replace the full path with the symlink version (e.g. `/sys/class/backlight/acpi_video0/brightness`) the change from the C code fails as the kernel refuses the mismatch between the `/sys/class` link and the actual `/sys/devices` ID. What that tells me is the shell either uses `readlink -f` before redirection (or in the special case where the destination is on `sysfs`). Will have to pull bash code to see. – David C. Rankin Jul 11 '19 at 04:09
  • I can produce differing results between SuSE and Arch, (4.4 v. 5.1 kernels), so I'm not sure whether this is kernel version of config dependent. – David C. Rankin Jul 11 '19 at 04:40
  • @n.m. - yes, no problem reading with the symlink. The problem was trying to write to the symlink on opensuse, but the write to the `/sys/devices` directory works (I also have Nvidia involved). On Arch, I can read and write with the symlink itself, so I'm not sure where the difference is. I cannot pin it down in the documentation as I initially thought when testing opensuse alone. – David C. Rankin Jul 11 '19 at 05:20
  • Probably some selinux/systap/fanotify/audit/etc framework gone amok. FWIW, trying to resolve links by hand may be actually harmful on linux -- many `/proc` and `/sys` files are implemented as fake "magical" symlinks where the target path is purely informative. –  Jul 11 '19 at 05:37