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; } ============================================================
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.