#1135862 systemd-sysctl: does not handle same-line comments in conf files

Package:
systemd
Source:
systemd
Description:
system and service manager
Submitter:
Graeme Vetterlein
Date:
2026-05-09 21:05:02 UTC
Severity:
normal
Tags:
#1135862#5
Date:
2026-05-06 18:33:07 UTC
From:
To:
(the host this is being reported from is the same host used in testing .. which is isolated)


Given a .conf file containing (literally):
net.ipv6.conf.all.forwarding=1  # not 2345

The two commands behave quite differently and produce very different results**.
I was able to simulate this behaviour using write(2) vs printf(3), I will attach the
code samples.

** systcl(8) sets it to 1, systemd-sysctl(8) sets it to 5

Looking at the code of sysctl(8) , it APPEARS to use printf(3) but the file
pointer was not created with fopen(3) but rather fprocopen() [ uses openat(2) ]
so ultimately it uses write(2).


Other interesting examples to test are:

net.ipv6.conf.all.forwarding=12345

and

net.ipv6.conf.all.forwarding=0 # must be 0 as 1 is a security breach

Which produce surprising results, esp if you use sysctl(8) to test your setting prior to "go live"

(esp. as the effect of setting net.ipv6.conf.all.forwarding to 6 seems to be the same as 0)









# cat /etc/sysctl.d/60-bogus.conf
net.ipv6.conf.all.forwarding=1  # not 2345

# sysctl -w net.ipv6.conf.all.forwarding=0
net.ipv6.conf.all.forwarding = 0
# sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 0


# strace /usr/sbin/sysctl --system

...

readlink("/proc/sys/net/ipv6/conf/all/forwarding", 0x7ffd0d33b0b0, 1023) = -1 EINVAL (Invalid argument)
openat(AT_FDCWD, "/proc/sys/net/ipv6/conf/all/forwarding", O_WRONLY|O_TRUNC) = 3
write(3, "1  # not 2345\n", 14)         = 1
close(3)                                = 0
write(1, "net.ipv6.conf.all.forwarding = 1"..., 45net.ipv6.conf.all.forwarding = 1  # not 2345
) = 45
close(1)                                = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++
# sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 1

.....



# sysctl -w net.ipv6.conf.all.forwarding=0
net.ipv6.conf.all.forwarding = 0

# sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 0

# strace  /usr/lib/systemd/systemd-sysctl
...

read(3, "4194304\n", 9)                 = 8
close(3)                                = 0
openat(AT_FDCWD, "/proc/sys/net/ipv6/conf/all/forwarding", O_RDWR|O_NOCTTY|O_CLOEXEC) = 3
fcntl(3, F_GETFL)                       = 0x8002 (flags O_RDWR|O_LARGEFILE)
fstat(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
read(3, "0\n", 15)                      = 2
lseek(3, 0, SEEK_SET)                   = 0
write(3, "1  # not 2345\n", 14)         = 1
write(3, "  # not 2345\n", 13)          = 1
write(3, " # not 2345\n", 12)           = 1
write(3, "# not 2345\n", 11)            = 1
write(3, " not 2345\n", 10)             = 1
write(3, "not 2345\n", 9)               = 1
write(3, "ot 2345\n", 8)                = 1
write(3, "t 2345\n", 7)                 = 1
write(3, " 2345\n", 6)                  = 1
write(3, "2345\n", 5)                   = 1
write(3, "345\n", 4)                    = 1
write(3, "45\n", 3)                     = 1
write(3, "5\n", 2)                      = 1
write(3, "\n", 1)                       = 1
close(3)                                = 0
exit_group(0)                           = ?

# sysctl net.ipv6.conf.all.forwarding
net.ipv6.conf.all.forwarding = 5





# apt-file search /usr/lib/systemd/systemd-sysctl
systemd: /usr/lib/systemd/systemd-sysctl

# apt show  systemd
WARNING: terminal is not fully functional
Press RETURN to continue

Package: systemd
Version: 257.9-1~deb13u1
Priority: important

$ cat /proc/version
Linux version 6.12.85+deb13-amd64 (debian-kernel@lists.debian.org) (x86_64-linux-gnu-gcc-14 (Debian 14.2.0-19) 14.2.0, GNU ld (GNU Binutils for Debian) 2.44) #1 SMP PREEMPT_DYNAMIC Debian 6.12.85-1 (2026-04-30)

$ cat /etc/debian_version
13.4

#1135862#10
Date:
2026-05-06 19:22:01 UTC
From:
To:

#1135862#17
Date:
2026-05-07 14:44:51 UTC
From:
To:
In case it wasn't obvious the line:

(the host this is being reported from is the same host used in testing .. which is isolated)

Should read:

(the host this is being reported from is NOT the same host used in testing .. which is isolated)

#1135862#22
Date:
2026-05-09 13:32:43 UTC
From:
To:
Somebody has changed the title of the bug from

   Bug#1135862: Acknowledgement (systemd: /usr/lib/systemd/systemd-sysctl behaves quite differently to /usr/sbin/sysctl, setting wrong values)

to

	systemd-sysctl: does not handle same-line comments in conf files

This is confusing and inaccurate . The bug does NOT relate to comments , as the example

net.ipv6.conf.all.forwarding=12345

Shows .

Any reference to inline comments will likely be rejected a RTFM. Please resect the many man hours spent digging into this issue, to raise a accurate bug report.

#1135862#27
Date:
2026-05-09 18:54:53 UTC
From:
To:
retitle 1135862 systemd-sysctl behaves differently than sysctl and sets the wrong values
thanks

My apologies, I misread your examples, as two of them specifically
demonstrated a problem with comments. You're right, *that* example shows
there are multiple issues here: there's an issue of not handling
same-line comments, *and* an issue of not handling some values
correctly.

I've changed the title of the issue to reflect the more general issue.
Thank you.

#1135862#34
Date:
2026-05-09 21:04:14 UTC
From:
To:
I suspect there is a single bug here, but the behaviour differs. This
and another IPv6 related new bug held me up for several weeks, so I've
spent a lot of time looking at this.


I initially also thought it was related to treatment of comments .
However after quite a lot of testing and code inspection, it appears the
root cause is


     sysctl(8) uses write(2)

     systemd variant uses some form of stream IO (e.f prinntf) **


** so when a write(2) of a 10 byte buffer returns "1" (byte sent) ,
systemd keeps calling , until all 10 bytes are consumed. sysctl(8) just
stops at 1 byte

*** that said I've seen write(2) also send many bytes ... so , a bit
more complicated.




The interaction is then how the kernel level code accepts the IO for
this particular /proc variable , it accepts just 1 byte (and seems to
only differentiate "1" or not "1" ..so "6" acts like "0")

for other /proc settings the kernel side obviously behaves differently ,
since for example:


cat /proc/sys/kernel/version
#111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026


Lots of bytes, including a '#'  :-)


(BTW, from a different system, just a for instance)



The line in sysctl.c is:


   } else {
             if (0 < fprintf(fp, "%s\n", value))
                 rc = EXIT_SUCCESS;


Which looks like regular printf()  but that "fp" comes, not from
fopen(3) but from:


        if ((fp = fprocopen(path, "w")) == NULL) {

     ...


Then, via a long tortuous route , ends up at close() doing: (this is
from memory now, it's a while since I traced it)


     if (cookie->final) {
         len = write(cookie->fd, cookie->buf, cookie->offset);


There is a whole bunch of special cases "delimiter characters" etc  and
a liberal scattering of gotos :-)