Bug 19182 - Incomplete information given from files in /proc/sys/* when doing a single-byte read
Summary: Incomplete information given from files in /proc/sys/* when doing a single-by...
Status: RESOLVED WILL_NOT_FIX
Alias: None
Product: Other
Classification: Unclassified
Component: Other (show other bugs)
Hardware: All Linux
: P1 normal
Assignee: Alexey Dobriyan
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-09-27 21:27 UTC by Christoph Biedl
Modified: 2012-08-13 16:42 UTC (History)
2 users (show)

See Also:
Kernel Version: 2.6.35.4
Subsystem:
Regression: No
Bisected commit-id:


Attachments

Description Christoph Biedl 2010-09-27 21:27:58 UTC
Byte of byte reads from certain files in /proc/sys/* cause only the
first character of a numeric value to be returned.

The result buffer in proc_put_long in kernel/sysctl.c (in 2.6.32
this is in __do_proc_dointvec) is wrongly limited to the number of
bytes the application has asked for:

        if (len > *size)
                len = *size;
        if (copy_to_user(*buf, tmp, len))
                return -EFAULT;

This actually limits the length of the result to the size of the
read request.  If that one is too small, bad luck.  In other words:
These /proc files must be read in a single request.  In even other
words, the information read from such files varies depending on
whether the read operation is done using a buffer, or several times
reading a single byte (or small number of bytes).

The bug is exposed in the fw_conntrack plugin (actually a shell script)
of munin 1.4 when using dash as /bin/sh, something the Debian
distribution will do in the upcoming "squeeze" release.  The command
in question is

    read MAX < /proc/sys/net/ipv4/netfilter/ip_conntrack_max

dash reads in single bytes, causing ...

    open("/proc/sys/net/ipv4/netfilter/ip_conntrack_max", O_RDONLY) = 3
    fcntl(0, F_DUPFD, 10)                   = 11
    close(0)                                = 0
    fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
    dup2(3, 0)                              = 0
    close(3)                                = 0
    read(0, "3", 1)                         = 1
    read(0, "", 1)                          = 0

... only the first letter to be read.

Compared to bash which reads a buffer:

    open("/proc/sys/net/ipv4/netfilter/ip_conntrack_max", O_RDONLY) = 3
    fcntl(0, F_GETFD)                       = 0
    fcntl(0, F_DUPFD, 10)                   = 10
    fcntl(0, F_GETFD)                       = 0
    fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
    dup2(3, 0)                              = 0
    close(3)                                = 0
    ioctl(0, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fff7046cdf0) = -1 ENOTTY (Inappropriate ioctl for device)
    lseek(0, 0, SEEK_CUR)                   = 0
    read(0, "32020\n", 128)                 = 6


How to reproduce:

Run the attached program, optionally with a file name of an entry in
/proc/sys, e.g.

    ./read-proc /proc/sys/net/netfilter/nf_conntrack_udp_timeout_stream


Expected behaviour:

Two lines showing the same number.  The actual value depends on the
kernel variable and system settings.


Observed behaviour:

The second line shows only a single digit.


============================================================
#!/usr/bin/perl

my $file = $ARGV[0] || "/proc/sys/net/nf_conntrack_max";
my $fh;
my $buf;

# buffered read
open ($fh, '<', $file) or die;
my $n_read = sysread ($fh, $buf, 256);
close ($fh);
print $buf;

# bytewise read
open ($fh, '<', $file) or die;
while (1) {
    $n_read = sysread ($fh, $buf, 1);
    if ($n_read == 0) {
	print "\n";
	close ($fh);
	last;
    }
    print $buf;
}
============================================================
Comment 1 Alexey Dobriyan 2010-11-14 14:00:52 UTC
No, the actual bug is this code triggering on second read(2):

if (... || (*ppos && !write)) {
        *lenp = 0;
        return 0;
}

Maybe it tries to prevent you from reading incoherent value.

Note You need to log in before you can comment on or make changes to this bug.