
Linux kernel: CVE-2017-6074: DCCP double-free vulnerability (local root) - QUFB
http://seclists.org/oss-sec/2017/q1/471
======
jlgaddis

      $ echo "install dccp /bin/true" >> /etc/modprobe.d/dccp.conf
      $ sudo rmmod dccp   # in case it's already loaded
    

This is a good idea for any modules you don't expect to ever need. In my case:

    
    
      $ cat /etc/modprobe.d/disabled_modules.conf
      install appletalk /bin/true
      install bluetooth /bin/true
      install cramfs /bin/true
      install dccp /bin/true
      install firewire-core /bin/true
    
      *snipped*
    
      install tipc /bin/true
      install udf /bin/true
      install usb-storage /bin/true
      install vfat /bin/true
    

The original -- _to me_ \-- recommendations for this were found in some
"hardening guide" for RHEL (CIS, NSA, etc.), although I don't remember which.

See also the "modprobe.blacklist=" kernel parameter, which you'll have to use
for "modules" that are compiled into the kernel itself (i.e., they are not
actually loadable kernel modules).

15 years ago, when building your own kernels was a normal everyday thing, I
simply built my kernels with everything compiled in and modules disabled. This
(would have) prevented attacks such as kernel-level rootkits.

In addition, "one neat trick" was that you could halt (not poweroff) the
machine (!) -- such as in the case of a Linux box simply acting as a
router/firewall -- and the kernel would still be running. Good luck
compromising that! :-)

~~~
rcdmd
Great advice. Using /bin/false also works, as I found on the Arch Wiki. Do you
know what the difference is?

    
    
      modprobe dccp #with /bin/false fails with
      
      modprobe: ERROR: Error running install command for dccp
      modprobe: ERROR: could not insert 'dccp': Operation not permitted

~~~
throwaway2048
/bin/true sets the return code to 0, signifying success to the modprobe
process, /bin/false sets it to 1, signifying failue, and producing said
message.

Due to the way modprobe may be hooked by various things, its best to use
/bin/true to ensure less breakage.

------
geofft
This is a good reason for systems running untrusted code to disable module
automatic loading. Almost nobody uses DCCP, and as a result, almost nobody
_looks at_ the DCCP code, writes bad userspace apps that trigger kernel bugs
that get debugged, etc. We rarely see double-frees in the TCP or UDP
implementations.

On my Debian kernel, CONFIG_IP_DCCP is set to "m" (in /boot/config-`uname
-r`), which means that DCCP support is built as a module. The code isn't
loaded until the first program tries to call socket(...IPPROTO_DCCP). At that
point, the kernel will look at /proc/sys/kernel/modprobe and run that program,
/sbin/modprobe by default, to load dccp.ko.

Automatic module loading is great when e.g. udev runs and detects what
hardware you have, but it's probably not something you'd ever need once a
system has completed boot. A very simple hardening measure for machines
running untrusted unprivileged code is to echo /bin/false >
/proc/sys/kernel/modprobe, late in the boot process (e.g., in /etc/rc.local).

The downside is that system administrator won't be able to run tools that
require loading modules, of which iptables is probably the most notable one. A
better option than /bin/false is a shell script that logs its arguments to
syslog, e.g., `logger -p authpriv.info -- "Refused modprobe $*"`. The sysadmin
can manually run modprobe on whatever module name got syslogged (or
temporarily set /proc/sys/kernel/modprobe back to /sbin/modprobe). And you can
alert on that syslog line to see if there's an attack in progress.

(Does anyone know if it's possible to disable module auto-loading for a
tree/namespace of processes, e.g. a Docker container, but keep it working for
the base system?)

~~~
blockoperation
...or just roll your own kernel with CONFIG_MODULES=n.

If you only need a specific set of modules, you might as well just build them
in and just forget LKMs altogether.

It's not very convenient when you run into some obscure hardware/fs/protocol,
but depending on your use case, that might not be an issue (and even in the
rare case where it does come up, it's not the end of the world – a minimal
kernel build only takes a couple of minutes on a reasonably fast machine).

The only case where LKMs really seem necessary (besides in generic distro
kernels) is for module development/debugging, but you're probably not going to
do that with a production kernel anyway.

~~~
kbenson
The last thing I want is to be manually rolling package updates for kernels.
That's one of the reasons I use a distro in the first place, to offload
testing to a dedicated team of people. If I was still interested in that, I
would run Linux From Scratch again.

As soon as you've administered 10+ servers at the same time, your patience for
variation from the norm and manual patch building goes way down.

Once you've administered 100+ servers at the same time, any thoughts of doing
that go right out the window. along with the person that brought it up.

At that point, any changes are diffs applied to the original package (or a
brand new package from scratch) that you put in your local repo. Kernels
actually get updates fairly often, which means you either have a lot of work,
or you ignore the non-essential updates. Neither are ideal.

~~~
blockoperation
I can see why it might be a burden when you're dealing with hundreds of
servers, but on a personal machine, where the occasional disruption won't get
you sacked, it's pretty straightforward (though I say this as someone who
follows kernel development as a hobby and has the time to track down bugs).

~~~
geofft
On the other hand, for a personal machine, caring about local root exploits is
almost certainly outside your threat model. You have a
[https://xkcd.com/1200/](https://xkcd.com/1200/) architecture, where
everything other than software updates is running as uid 1000. The things you
_actually_ care about, your emails, your IMs, your tax documents, etc., are
all accessible to uid 1000. Any random malware you might download will run as
uid 1000. root is honestly a less interesting target. And the non-root account
runs sudo often enough that an attacker with access to your account can get to
root with a bit of patience, anyway, no kernel exploits required.

I used to maintain a laptop with two user accounts, one of which I used for
running sudo and doing important work, and one of which had the Flash and Java
plugins enabled and was used for Pandora, YouTube, etc. It sorta worked, but
it was a pain, and I eventually gave up on it. If you do have a setup like
this, then caring about local root exploits starts to make a bit of sense.

I now have a Chromebook, which sandboxes any attacker-controlled executable
code on the machine. If you actually care about the security of your personal
computer, do that, or get Qubes or something—and just use the vendor's
provided OS and keep it up-to-date.

~~~
blockoperation
Multiple accounts can be quite usable if you get the separation right.
Separate uids for personal emails/banking and porn browsing should be a given,
at least.

~~~
kbenson
At that point, why stop at separate uids and not just use separate virtual
servers? It's a bit more costly in space and RAM (when running), but it's
pretty good at reducing the attack surface. If you _really_ want to be
paranoid, restore from a snapshot every time you start the VM, and
occasionally start it just to update it and create a new snapshot. Even if an
attacker does get a local account, they have to do something useful with it
before you close the virt, essentially destroying anything local they've set
up.

------
wronskian
The diff from the patch:

    
    
      - goto discard;
      + consume_skb(skb);
      + return 0;
    

One of the rare cases in the wild where a goto really was considered harmful!
;-)

~~~
IshKebab
Rare? What about goto fail?

~~~
sebcat
in 'goto fail', the problem looked like:

    
    
        if (foo() != 0)
          goto fail;
          goto fail;
    

That extra line of code could just as likely have been a return statement or
anything else. The problem was not with the goto statement itself.

Edited to add: 'goto fail' is a valid construct and can be used to handle
finalization in C functions. Consider:

    
    
        int
        foo(char *bar, int baz) {
          sometype_t *obj = NULL;
          int fd = -1;
          
          if ((obj = sometype_new(bar, baz)) == NULL) {
            goto fail;
          }
          if ((fd = open(sometype_path(obj), O_RDONLY)) < 0) {
            goto fail;
          }
          /* ... */
          return 0;
        fail:
          if (fd < 0) {
            close(fd);
          }
          if (obj != NULL) {
            sometype_free(obj);
          }
          return -1;
        }
    

There's nothing inherently wrong about that.

~~~
Normal_gaussian
It is often much clearer to use goto than using a wrapper and switching on
enum'ed return value to destruct a failed transaction.

------
unmole
Here's an overview of DCCP I wrote if anyone is interested:
[https://www.anmolsarma.in/post/dccp/](https://www.anmolsarma.in/post/dccp/)

------
0x0
Status in Debian: [https://security-
tracker.debian.org/tracker/CVE-2017-6074](https://security-
tracker.debian.org/tracker/CVE-2017-6074)

~~~
yourcelf
Status in Ubuntu: [https://people.canonical.com/~ubuntu-
security/cve/2017/CVE-2...](https://people.canonical.com/~ubuntu-
security/cve/2017/CVE-2017-6074.html)

~~~
ptrincr
The below should be followed at the users own risk. Perhaps someone can
confirm if the following is sane.

For Ubuntu versions:

    
    
      Ubuntu 12.04.3 LTS
      Ubuntu 14.04.5 LTS
    

The following should get you patched up, you will need a reboot though:

    
    
      sudo apt-get update
      sudo apt-get install linux-headers-3.13.0-110 linux-headers-3.13.0-110-generic linux-image-3.13.0-110-generic
    

After reboot:

    
    
      uname -a
      Linux hostname 3.13.0-110-generic #157-Ubuntu
    

Any Digital Ocean users may need to power down their droplet and switch the
kernel to the following version:

    
    
      DigitalOcean GrubLoader v0.2 (20160714) Ubuntu
    

Without this the new kernel may not be used.

~~~
lobster_johnson
Digital Ocean hasn't required that you change the kernel through the UI for
some time now. Are you sure that's necessary?

~~~
jsamuel
It's only necessary for older servers (e.g. Ubuntu 12.04 and 14.04) where
people haven't done this already.

~~~
vdloo
you can also use the API for this
[https://developers.digitalocean.com/documentation/v2/#change...](https://developers.digitalocean.com/documentation/v2/#change-
the-kernel)

for example for precise the kernel ID is 7515 for the "DigitalOcean GrubLoader
v0.2 (20160714) Ubuntu" kernel. If you do this be sure to double-check the id
by listing the kernels using the API though. You can also do this with
libcloud using 'ex_change_kernel' from the 'DigitalOcean_v2_NodeDriver'

------
lossolo
Already fixed on ubuntu

linux (4.4.0-64.85) xenial; urgency=low

    
    
      * CVE-2017-6074 (LP: #1665935)
        - dccp: fix freeing skb too early for IPV6_RECVPKTINFO
    
     -- Stefan Bader <stefan.bader@canonical.com>  Mon, 20 Feb 2017 10:06:47

------
Wheaties466
Let me see if I got this right, This can be used to DOS a system by consuming
all free memory?

In the CVE it almost hints that this specifically is UDP related.

Am I right in thinking this?

~~~
geofft
This is a double-free vulnerability. In userspace, this would basically be

    
    
        void *x = malloc(16);
        free(x);
        ...
        free(x);
    

When malloc makes the allocation, it actually allocates a bit more than 16
bytes so it knows how much to free. Usually what it'll do is store a structure
right _before_ the pointer it returns; for instance, it might allocate 24
bytes, use the first 8 bytes to store the number 24, and then return the rest
of that allocation. Then, when free is called, it subtracts 8 from what it was
given, and frees that many bytes. (More complicated malloc implementations
will have more complicated structures, possibly including pointers to other,
shared structures.)

If you call free a second time on the same pointer, a lot of things could
happen. In particular, if there was another call to malloc() in between, it
might reuse that same location, and you might free _that_. Or if some other
sequence of allocations happened, there might be a completely nonsensical
number stored at x-8.

If the second call "frees" data that's still in use, two parts of the program
will be writing to the same memory. Maybe one thing is storing the return
value of a call, and the other thing is storing the current UID. So when the
first thing writes 0, now you're UID 0.

Alternatively, if the second call frees something nonsensical, you might
"free" memory that was never the allocator's in the first place - and cause
the next call to malloc() to return a pointer to something important elsewhere
in memory, perhaps even the program's stack frame. If the program then reads
attacker-controlled data into that newly-allocated memory, the attacker takes
over the program's control flow.

DCCP is a protocol that is kind of like UDP, in that it's message-oriented,
but kind of like TCP, in that it's reliable and implements congestion control.
It's not very common, and uncommon networking protocols have been a historic
good source of vulnerabilities in the Linux kernel.

~~~
jtchang
I assume the reason why this can happen is because the default implementation
of malloc/free doesn't check if the pointer has already been free'ed right?
And the reason it doesn't check is because of performance reasons?

~~~
coldpie
How do you detect a "pointer that has already been freed"?

~~~
mikeash
You could keep a separate list of all live allocations. If free() is called
with a pointer that's not on the list, it's an error.

This won't immediately detect the case where you free, something else mallocs
in that exact spot, and then you free again. It will eventually catch it when
the something else tries to call free. It will catch cases where the second
free call happens before the memory is reused, or when the memory is reused
but the new chunk starts earlier.

I assume this isn't done because maintaining and checking the list would cost
too much.

~~~
xenadu02
If the bookkeeping weren't so massive you could cycle through the entire
64-bit address space before re-issuing the same memory address. That would be
similar to a bump allocator; once all the allocations on a page were freed you
could unmap it so except for pathological cases you couldn't explode the
number of page table entries too horribly.

At 1 TB/sec of allocations it would take 500 years to reach the end of the
64-bit address space in the kernel. (It's actually 544 years ignoring leap
years and leap seconds, but subtract 44 years for reserved address ranges).

The benefit would be making it impossible to use-after-free because the same
address would never be re-used.

~~~
majewsky
The problem isn't the bookkeeping. It's the massive implicit memory leak that
you get when there's only one 4-byte allocation left on your 4-KB page.

~~~
xenadu02
That's what I meant by "except for pathological cases". Existing memory
allocators have the same problems with fragmentation.

------
arca_vorago
DCCP is something even a basic hardening should already have taken care of...
but of course many people don't do those.

Quick solution: "echo "install dccp /bin/true" >>
/etc/modprobe.d/modprobe.conf"

~~~
frederikvs
what would be included in a "basic hardening"?

~~~
jlgaddis
The "Center for Internet Security" Benchmarks [0] -- such as the one for
Ubuntu 16.04 (PDF) [1] -- address your question.

The DISA IASE [2] publishes hundreds of "Security Technical Implementation
Guides" (STIGs) for various operating systems (including several Linux
distributions [3]), software applications, networking devices, and so on.

The OpenSCAP [4] Security Guides [5] are also a good reference. They are
primarily aimed at compliance w/ security requirements (C2S, PCI-DSS, USGCB)
-- for example, the "U.S. Government Commercial Cloud Services (C2S)" [6].

I don't 100% fully implement any of these, but I have a lot of RHEL and CentOS
boxes publicly accessible on the Internet and they don't _become_ accessible
until they've had the majority of these recommendations implemented.

P.S. Look around. There are tons of shell scripts, Ansible roles, Puppet
modules, etc., that will take care of 90% of this for you. There's no excuse
for having public-facing machines that aren't locked down.

[0]:
[https://benchmarks.cisecurity.org/downloads/benchmarks/](https://benchmarks.cisecurity.org/downloads/benchmarks/)

[1]:
[https://benchmarks.cisecurity.org/tools2/linux/cis_ubuntu_li...](https://benchmarks.cisecurity.org/tools2/linux/cis_ubuntu_linux_16.04_lts_benchmark_v1.0.0.pdf)

[2]:
[http://iase.disa.mil/Pages/index.aspx](http://iase.disa.mil/Pages/index.aspx)

[3]: [http://iase.disa.mil/stigs/os/unix-
linux/Pages/index.aspx](http://iase.disa.mil/stigs/os/unix-
linux/Pages/index.aspx)

[4]: [https://github.com/OpenSCAP/scap-security-
guide/](https://github.com/OpenSCAP/scap-security-guide/)

[5]: [https://www.open-scap.org/security-policies/choosing-
policy/](https://www.open-scap.org/security-policies/choosing-policy/)

[6]: [http://static.open-scap.org/ssg-guides/ssg-
rhel7-guide-C2S.h...](http://static.open-scap.org/ssg-guides/ssg-
rhel7-guide-C2S.html)

~~~
frederikvs
Thanks, very interesting reads.

Makes one wonder why these things aren't default on a linux distro...

As an embedded software developer it's a bit inconvenient that it's all
focused towards the big distributions (ubuntu, RHEL, ...), but still
interesting input for securing an embedded linux device :-)

~~~
jlgaddis
While these guides are aimed at specific distributions, many (most?) of the
concepts "translate" quite easily to other flavors of Linux. sysctl's, for
example, might be configured in a different file but they will still exist and
work the same (disclaimer: _usually_!).

------
DannyBee
syzkaller strikes again!

------
vasili111
Does the Gentoo hardened vulnerable too?

------
m00dy
It looks legit.

------
neoeldex
I can't wait untill the linux kernel is ported to Rust ^^

~~~
bsdnoob
Its never going to happen

~~~
disconnected
In the meantime, there's Redox OS:

[https://www.redox-os.org/](https://www.redox-os.org/)

------
nyiihahah
Why isn't there any Cryptocurrency for fuzzing well-known softwares?
[https://security.stackexchange.com/questions/152036/why-
isnt...](https://security.stackexchange.com/questions/152036/why-isnt-there-
any-cryptocurrency-for-fuzzing-well-known-softwares)

~~~
infogulch
Cryptocurrencies need a relatively stable coin generation scheme. Hits from
fuzzing is typically very bursty, often produces nothing, and changing the
fuzzing scheme slightly can reveal completely different results.

It's just too variable.

