stdbuf with setuid/capabilities
Asked Answered
L

2

3

I am reading output from another process which generates output (slow and infinite). Because I want to read this data in real-time I use "stdbuf -oL" (line-buffered, data is text). I do not have control of the generating process so I cannot modify the source to force flushing.

So far stdbuf works just fine, however the process uses SOCK_RAW and needs either to be run as root, have setuid(0) or the cap_net_raw capability. When running as non-root with setuid or capabilities stdbuf seems to be ignored. Let me demonstrate the problem:

This is a simple writer:

#include <stdio.h>
#include <unistd.h>

int main(){
        int i;
        for ( i = 0;; i++){
                fprintf(stdout, "%d\n", i);
                sleep(1);
        }
}

And a simple reader:

#include <stdio.h>

int main(){
        char* line = NULL;
        size_t n = 0;
        while (getline(&line, &n, stdin) != -1 ) {
                fputs(line, stdout);
        }
}

As expected, by executing ./writer | ./reader nothing shows up until the buffer is filled. Prepending stdbuf -oL enables line-buffering and I get the lines into the reader:

% stdbuf -oL ./writer | ./reader
0
1
2
...

But if I add cap_net_raw+ep it stops working:

% sudo setcap cap_net_raw+ep ./writer
% stdbuf -oL ./writer | ./reader
(no output)

The same behaviour is observed when using setuid:

% sudo chown root:root ./writer
% sudo chmod +s ./writer
% stdbuf -oL ./writer | ./reader
(no output)

I'm interested in understanding why this happens and how I can continue to use stdbuf without running as root. I admit that I do not fully understand what setuid is doing behind the scenes.

Leavenworth answered 30/11, 2012 at 11:7 Comment(3)
What function is used within getline()?Timecard
The example uses GNU getline (available with -std=gnu99), but the behaviour is the same with fread.Leavenworth
Have you looked at it with strace?Timecard
D
3

From looking at the stdbuf source code it looks like it works by setting LD_PRELOAD. There are of course security concerns using LD_PRELOAD with setuid executables or sudo.

One suggestion I found was to disable the noatsecure selinux attribute for your executable.

Another, simpler, option would be to avoid stdbuf and simply call fflush(stdout) from your source code directly.

Disprize answered 30/11, 2012 at 16:28 Comment(2)
Sadly I don't have control of the source for the writer so I cannot modify it. But thanks for the explanation about stdbuf, it makes sense to disallow LD_PRELOAD for setuid.Leavenworth
There is also a man page for the FreeBSD port of stdbuf showing the use of LD_PRELOAD: libstdbufOrphism
O
3

Solution without LD_PRELOAD

You can use the unbuffer utility which is part of the expect (expect-devel) package. unbuffer is a very short expect script. It does not need LD_PRELOAD because it uses another trick. expect creates a pseudo terminal (like xterm or ssh) so the process executed using unbuffer is fooled to think it is writing to an interactive device therefore by default it uses line buffering on stdout.

Usage in your case:

unbuffer ./writer | ./reader

If stdbuf works with the program unbuffer would work with a high probability too. Because LD_PRELOAD poses some limitations unbuffer has advantages over stdbuf. Contrary to stdbuf it will work with these sorts of executables:

  • setuid
  • with file capabilities
  • statically linked
  • not using standard libc
Orphism answered 4/9, 2013 at 21:33 Comment(2)
You mentioned that using a pty make the buffer become a line buffer. What does this really mean? That if I had an infinite line without a \n, nothing will ever come out? Or is there some limit? What about turning buffering off completely, so that at most 1 byte exists in the buffer?Liturgist
@Liturgist Many systems including GNU/Linux try to be ISO C compatible. There are three modes: unbuffered (everything is sent as soon as possible), fully buffered (buffer defined by its size), line buffered (buffers a single line of the text). For the line buffering the buffer still has a fixed size so when it is filled, it is flushed. No buffering "unlimited" of extremely long lines is happening. See open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf part 7.21.3 FilesOrphism

© 2022 - 2024 — McMap. All rights reserved.