Merge pull request #3959 from bluca/fuzzers

Problem: mtrie use of non-tail recursion leads to stack overflow

@@ -35,6 +35,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.

 #include <new>
 #include <algorithm>
+#include <list>

 #include "err.hpp"
 #include "macros.hpp"
@@ -69,85 +70,88 @@ template <typename T> generic_mtrie_t<T>::~generic_mtrie_t ()
 template <typename T>
 bool generic_mtrie_t<T>::add (prefix_t prefix_, size_t size_, value_t *pipe_)
 {
-    return add_helper (prefix_, size_, pipe_);
-}
-
-template <typename T>
-bool generic_mtrie_t<T>::add_helper (prefix_t prefix_,
-                                     size_t size_,
-                                     value_t *pipe_)
-{
-    //  We are at the node corresponding to the prefix. We are done.
-    if (!size_) {
-        const bool result = !_pipes;
-        if (!_pipes) {
-            _pipes = new (std::nothrow) pipes_t;
-            alloc_assert (_pipes);
+    generic_mtrie_t<value_t> *it = this;
+
+    while (size_) {
+        const unsigned char c = *prefix_;
+
+        if (c < it->_min || c >= it->_min + it->_count) {
+            //  The character is out of range of currently handled
+            //  characters. We have to extend the table.
+            if (!it->_count) {
+                it->_min = c;
+                it->_count = 1;
+                it->_next.node = NULL;
+            } else if (it->_count == 1) {
+                const unsigned char oldc = it->_min;
+                generic_mtrie_t *oldp = it->_next.node;
+                it->_count = (it->_min < c ? c - it->_min : it->_min - c) + 1;
+                it->_next.table = static_cast<generic_mtrie_t **> (
+                  malloc (sizeof (generic_mtrie_t *) * it->_count));
+                alloc_assert (it->_next.table);
+                for (unsigned short i = 0; i != it->_count; ++i)
+                    it->_next.table[i] = 0;
+                it->_min = std::min (it->_min, c);
+                it->_next.table[oldc - it->_min] = oldp;
+            } else if (it->_min < c) {
+                //  The new character is above the current character range.
+                const unsigned short old_count = it->_count;
+                it->_count = c - it->_min + 1;
+                it->_next.table = static_cast<generic_mtrie_t **> (realloc (
+                  it->_next.table, sizeof (generic_mtrie_t *) * it->_count));
+                alloc_assert (it->_next.table);
+                for (unsigned short i = old_count; i != it->_count; i++)
+                    it->_next.table[i] = NULL;
+            } else {
+                //  The new character is below the current character range.
+                const unsigned short old_count = it->_count;
+                it->_count = (it->_min + old_count) - c;
+                it->_next.table = static_cast<generic_mtrie_t **> (realloc (
+                  it->_next.table, sizeof (generic_mtrie_t *) * it->_count));
+                alloc_assert (it->_next.table);
+                memmove (it->_next.table + it->_min - c, it->_next.table,
+                         old_count * sizeof (generic_mtrie_t *));
+                for (unsigned short i = 0; i != it->_min - c; i++)
+                    it->_next.table[i] = NULL;
+                it->_min = c;
+            }
         }
-        _pipes->insert (pipe_);
-        return result;
-    }

-    const unsigned char c = *prefix_;
-    if (c < _min || c >= _min + _count) {
-        //  The character is out of range of currently handled
-        //  characters. We have to extend the table.
-        if (!_count) {
-            _min = c;
-            _count = 1;
-            _next.node = NULL;
-        } else if (_count == 1) {
-            const unsigned char oldc = _min;
-            generic_mtrie_t *oldp = _next.node;
-            _count = (_min < c ? c - _min : _min - c) + 1;
-            _next.table = static_cast<generic_mtrie_t **> (
-              malloc (sizeof (generic_mtrie_t *) * _count));
-            alloc_assert (_next.table);
-            for (unsigned short i = 0; i != _count; ++i)
-                _next.table[i] = 0;
-            _min = std::min (_min, c);
-            _next.table[oldc - _min] = oldp;
-        } else if (_min < c) {
-            //  The new character is above the current character range.
-            const unsigned short old_count = _count;
-            _count = c - _min + 1;
-            _next.table = static_cast<generic_mtrie_t **> (
-              realloc (_next.table, sizeof (generic_mtrie_t *) * _count));
-            alloc_assert (_next.table);
-            for (unsigned short i = old_count; i != _count; i++)
-                _next.table[i] = NULL;
+        //  If next node does not exist, create one.
+        if (it->_count == 1) {
+            if (!it->_next.node) {
+                it->_next.node = new (std::nothrow) generic_mtrie_t;
+                alloc_assert (it->_next.node);
+                ++(it->_live_nodes);
+            }
+
+            ++prefix_;
+            --size_;
+            it = it->_next.node;
         } else {
-            //  The new character is below the current character range.
-            const unsigned short old_count = _count;
-            _count = (_min + old_count) - c;
-            _next.table = static_cast<generic_mtrie_t **> (
-              realloc (_next.table, sizeof (generic_mtrie_t *) * _count));
-            alloc_assert (_next.table);
-            memmove (_next.table + _min - c, _next.table,
-                     old_count * sizeof (generic_mtrie_t *));
-            for (unsigned short i = 0; i != _min - c; i++)
-                _next.table[i] = NULL;
-            _min = c;
-        }
-    }
+            if (!it->_next.table[c - it->_min]) {
+                it->_next.table[c - it->_min] =
+                  new (std::nothrow) generic_mtrie_t;
+                alloc_assert (it->_next.table[c - it->_min]);
+                ++(it->_live_nodes);
+            }

-    //  If next node does not exist, create one.
-    if (_count == 1) {
-        if (!_next.node) {
-            _next.node = new (std::nothrow) generic_mtrie_t;
-            alloc_assert (_next.node);
-            ++_live_nodes;
+            ++prefix_;
+            --size_;
+            it = it->_next.table[c - it->_min];
         }
-        return _next.node->add_helper (prefix_ + 1, size_ - 1, pipe_);
     }
-    if (!_next.table[c - _min]) {
-        _next.table[c - _min] = new (std::nothrow) generic_mtrie_t;
-        alloc_assert (_next.table[c - _min]);
-        ++_live_nodes;
+
+    //  We are at the node corresponding to the prefix. We are done.
+    const bool result = !it->_pipes;
+    if (!it->_pipes) {
+        it->_pipes = new (std::nothrow) pipes_t;
+        alloc_assert (it->_pipes);
     }
-    return _next.table[c - _min]->add_helper (prefix_ + 1, size_ - 1, pipe_);
-}
+    it->_pipes->insert (pipe_);

+    return result;
+}

 template <typename T>
 template <typename Arg>
@@ -158,261 +162,371 @@ void generic_mtrie_t<T>::rm (value_t *pipe_,
                              Arg arg_,
                              bool call_on_uniq_)
 {
+    //  This used to be implemented as a non-tail recursive travesal of the trie,
+    //  which means remote clients controlled the depth of the recursion and the
+    //  stack size.
+    //  To simulate the non-tail recursion, with post-recursion changes depending on
+    //  the result of the recursive call, a stack is used to re-visit the same node
+    //  and operate on it again after children have been visisted.
+    //  A boolean is used to record whether the node had already been visited and to
+    //  determine if the pre- or post- children visit actions have to be taken.
+    //  In the case of a node with (N > 1) children, the node has to be re-visited
+    //  N times, in the correct order after each child visit.
+    std::list<struct iter> stack;
     unsigned char *buff = NULL;
-    rm_helper (pipe_, &buff, 0, 0, func_, arg_, call_on_uniq_);
-    free (buff);
-}
-
-template <typename T>
-template <typename Arg>
-void generic_mtrie_t<T>::rm_helper (value_t *pipe_,
-                                    unsigned char **buff_,
-                                    size_t buffsize_,
-                                    size_t maxbuffsize_,
-                                    void (*func_) (prefix_t data_,
-                                                   size_t size_,
-                                                   Arg arg_),
-                                    Arg arg_,
-                                    bool call_on_uniq_)
-{
-    //  Remove the subscription from this node.
-    if (_pipes && _pipes->erase (pipe_)) {
-        if (!call_on_uniq_ || _pipes->empty ()) {
-            func_ (*buff_, buffsize_, arg_);
-        }
-
-        if (_pipes->empty ()) {
-            LIBZMQ_DELETE (_pipes);
-        }
-    }
-
-    //  Adjust the buffer.
-    if (buffsize_ >= maxbuffsize_) {
-        maxbuffsize_ = buffsize_ + 256;
-        *buff_ = static_cast<unsigned char *> (realloc (*buff_, maxbuffsize_));
-        alloc_assert (*buff_);
-    }
-
-    switch (_count) {
-        case 0:
-            //  If there are no subnodes in the trie, return.
-            break;
-        case 1:
-            //  If there's one subnode (optimisation).
-
-            (*buff_)[buffsize_] = _min;
-            buffsize_++;
-            _next.node->rm_helper (pipe_, buff_, buffsize_, maxbuffsize_, func_,
-                                   arg_, call_on_uniq_);
-
-            //  Prune the node if it was made redundant by the removal
-            if (_next.node->is_redundant ()) {
-                LIBZMQ_DELETE (_next.node);
-                _count = 0;
-                --_live_nodes;
-                zmq_assert (_live_nodes == 0);
+    size_t maxbuffsize = 0;
+    struct iter it = {this, NULL, NULL, 0, 0, 0, false};
+    stack.push_back (it);
+
+    while (!stack.empty ()) {
+        it = stack.back ();
+        stack.pop_back ();
+
+        if (!it.processed_for_removal) {
+            //  Remove the subscription from this node.
+            if (it.node->_pipes && it.node->_pipes->erase (pipe_)) {
+                if (!call_on_uniq_ || it.node->_pipes->empty ()) {
+                    func_ (buff, it.size, arg_);
+                }
+
+                if (it.node->_pipes->empty ()) {
+                    LIBZMQ_DELETE (it.node->_pipes);
+                }
             }
-            break;
-        default:
-            //  If there are multiple subnodes.
-            rm_helper_multiple_subnodes (buff_, buffsize_, maxbuffsize_, func_,
-                                         arg_, call_on_uniq_, pipe_);
-            break;
-    }
-}

-template <typename T>
-template <typename Arg>
-void generic_mtrie_t<T>::rm_helper_multiple_subnodes (
-  unsigned char **buff_,
-  size_t buffsize_,
-  size_t maxbuffsize_,
-  void (*func_) (prefix_t data_, size_t size_, Arg arg_),
-  Arg arg_,
-  bool call_on_uniq_,
-  value_t *pipe_)
-{
-    //  New min non-null character in the node table after the removal
-    unsigned char new_min = _min + _count - 1;
-    //  New max non-null character in the node table after the removal
-    unsigned char new_max = _min;
-    for (unsigned short c = 0; c != _count; c++) {
-        (*buff_)[buffsize_] = _min + c;
-        if (_next.table[c]) {
-            _next.table[c]->rm_helper (pipe_, buff_, buffsize_ + 1,
-                                       maxbuffsize_, func_, arg_,
-                                       call_on_uniq_);
-
-            //  Prune redundant nodes from the mtrie
-            if (_next.table[c]->is_redundant ()) {
-                LIBZMQ_DELETE (_next.table[c]);
-
-                zmq_assert (_live_nodes > 0);
-                --_live_nodes;
-            } else {
-                //  The node is not redundant, so it's a candidate for being
-                //  the new min/max node.
-                //
-                //  We loop through the node array from left to right, so the
-                //  first non-null, non-redundant node encountered is the new
-                //  minimum index. Conversely, the last non-redundant, non-null
-                //  node encountered is the new maximum index.
-                if (c + _min < new_min)
-                    new_min = c + _min;
-                if (c + _min > new_max)
-                    new_max = c + _min;
+            //  Adjust the buffer.
+            if (it.size >= maxbuffsize) {
+                maxbuffsize = it.size + 256;
+                buff =
+                  static_cast<unsigned char *> (realloc (buff, maxbuffsize));
+                alloc_assert (buff);
             }
-        }
-    }
-
-    zmq_assert (_count > 1);

-    //  Free the node table if it's no longer used.
-    switch (_live_nodes) {
-        case 0:
-            free (_next.table);
-            _next.table = NULL;
-            _count = 0;
-            break;
-        case 1:
-            //  Compact the node table if possible
-
-            //  If there's only one live node in the table we can
-            //  switch to using the more compact single-node
-            //  representation
-            zmq_assert (new_min == new_max);
-            zmq_assert (new_min >= _min && new_min < _min + _count);
-            {
-                generic_mtrie_t *node = _next.table[new_min - _min];
-                zmq_assert (node);
-                free (_next.table);
-                _next.node = node;
+            switch (it.node->_count) {
+                case 0:
+                    //  If there are no subnodes in the trie, we are done with this node
+                    //  pre-processing.
+                    break;
+                case 1: {
+                    //  If there's one subnode (optimisation).
+
+                    buff[it.size] = it.node->_min;
+                    //  Mark this node as pre-processed and push it, so that the next
+                    //  visit after the operation on the child can do the removals.
+                    it.processed_for_removal = true;
+                    stack.push_back (it);
+                    struct iter next = {
+                      it.node->_next.node, NULL, NULL, ++it.size, 0, 0, false};
+                    stack.push_back (next);
+                    break;
+                }
+                default: {
+                    //  If there are multiple subnodes.
+                    //  When first visiting this node, initialize the new_min/max parameters
+                    //  which will then be used after each child has been processed, on the
+                    //  post-children iterations.
+                    if (it.current_child == 0) {
+                        //  New min non-null character in the node table after the removal
+                        it.new_min = it.node->_min + it.node->_count - 1;
+                        //  New max non-null character in the node table after the removal
+                        it.new_max = it.node->_min;
+                    }
+
+                    //  Mark this node as pre-processed and push it, so that the next
+                    //  visit after the operation on the child can do the removals.
+                    buff[it.size] = it.node->_min + it.current_child;
+                    it.processed_for_removal = true;
+                    stack.push_back (it);
+                    if (it.node->_next.table[it.current_child]) {
+                        struct iter next = {
+                          it.node->_next.table[it.current_child],
+                          NULL,
+                          NULL,
+                          it.size + 1,
+                          0,
+                          0,
+                          false};
+                        stack.push_back (next);
+                    }
+                }
             }
-            _count = 1;
-            _min = new_min;
-            break;
-        default:
-            if (new_min > _min || new_max < _min + _count - 1) {
-                zmq_assert (new_max - new_min + 1 > 1);
-
-                generic_mtrie_t **old_table = _next.table;
-                zmq_assert (new_min > _min || new_max < _min + _count - 1);
-                zmq_assert (new_min >= _min);
-                zmq_assert (new_max <= _min + _count - 1);
-                zmq_assert (new_max - new_min + 1 < _count);
-
-                _count = new_max - new_min + 1;
-                _next.table = static_cast<generic_mtrie_t **> (
-                  malloc (sizeof (generic_mtrie_t *) * _count));
-                alloc_assert (_next.table);
-
-                memmove (_next.table, old_table + (new_min - _min),
-                         sizeof (generic_mtrie_t *) * _count);
-                free (old_table);
-
-                _min = new_min;
+        } else {
+            //  Reset back for the next time, in case this node doesn't get deleted.
+            //  This is done unconditionally, unlike when setting this variable to true.
+            it.processed_for_removal = false;
+
+            switch (it.node->_count) {
+                case 0:
+                    //  If there are no subnodes in the trie, we are done with this node
+                    //  post-processing.
+                    break;
+                case 1:
+                    //  If there's one subnode (optimisation).
+
+                    //  Prune the node if it was made redundant by the removal
+                    if (it.node->_next.node->is_redundant ()) {
+                        LIBZMQ_DELETE (it.node->_next.node);
+                        it.node->_count = 0;
+                        --it.node->_live_nodes;
+                        zmq_assert (it.node->_live_nodes == 0);
+                    }
+                    break;
+                default:
+                    //  If there are multiple subnodes.
+                    {
+                        if (it.node->_next.table[it.current_child]) {
+                            //  Prune redundant nodes from the mtrie
+                            if (it.node->_next.table[it.current_child]
+                                  ->is_redundant ()) {
+                                LIBZMQ_DELETE (
+                                  it.node->_next.table[it.current_child]);
+
+                                zmq_assert (it.node->_live_nodes > 0);
+                                --it.node->_live_nodes;
+                            } else {
+                                //  The node is not redundant, so it's a candidate for being
+                                //  the new min/max node.
+                                //
+                                //  We loop through the node array from left to right, so the
+                                //  first non-null, non-redundant node encountered is the new
+                                //  minimum index. Conversely, the last non-redundant, non-null
+                                //  node encountered is the new maximum index.
+                                if (it.current_child + it.node->_min
+                                    < it.new_min)
+                                    it.new_min =
+                                      it.current_child + it.node->_min;
+                                if (it.current_child + it.node->_min
+                                    > it.new_max)
+                                    it.new_max =
+                                      it.current_child + it.node->_min;
+                            }
+                        }
+
+                        //  If there are more children to visit, push again the current
+                        //  node, so that pre-processing can happen on the next child.
+                        //  If we are done, reset the child index so that the ::rm is
+                        //  fully idempotent.
+                        ++it.current_child;
+                        if (it.current_child >= it.node->_count)
+                            it.current_child = 0;
+                        else {
+                            stack.push_back (it);
+                            continue;
+                        }
+
+                        //  All children have been visited and removed if needed, and
+                        //  all pre- and post-visit operations have been carried.
+                        //  Resize/free the node table if needed.
+                        zmq_assert (it.node->_count > 1);
+
+                        //  Free the node table if it's no longer used.
+                        switch (it.node->_live_nodes) {
+                            case 0:
+                                free (it.node->_next.table);
+                                it.node->_next.table = NULL;
+                                it.node->_count = 0;
+                                break;
+                            case 1:
+                                //  Compact the node table if possible
+
+                                //  If there's only one live node in the table we can
+                                //  switch to using the more compact single-node
+                                //  representation
+                                zmq_assert (it.new_min == it.new_max);
+                                zmq_assert (it.new_min >= it.node->_min);
+                                zmq_assert (it.new_min
+                                            < it.node->_min + it.node->_count);
+                                {
+                                    generic_mtrie_t *node =
+                                      it.node->_next
+                                        .table[it.new_min - it.node->_min];
+                                    zmq_assert (node);
+                                    free (it.node->_next.table);
+                                    it.node->_next.node = node;
+                                }
+                                it.node->_count = 1;
+                                it.node->_min = it.new_min;
+                                break;
+                            default:
+                                if (it.new_min > it.node->_min
+                                    || it.new_max < it.node->_min
+                                                      + it.node->_count - 1) {
+                                    zmq_assert (it.new_max - it.new_min + 1
+                                                > 1);
+
+                                    generic_mtrie_t **old_table =
+                                      it.node->_next.table;
+                                    zmq_assert (it.new_min > it.node->_min
+                                                || it.new_max
+                                                     < it.node->_min
+                                                         + it.node->_count - 1);
+                                    zmq_assert (it.new_min >= it.node->_min);
+                                    zmq_assert (it.new_max
+                                                <= it.node->_min
+                                                     + it.node->_count - 1);
+                                    zmq_assert (it.new_max - it.new_min + 1
+                                                < it.node->_count);
+
+                                    it.node->_count =
+                                      it.new_max - it.new_min + 1;
+                                    it.node->_next.table =
+                                      static_cast<generic_mtrie_t **> (
+                                        malloc (sizeof (generic_mtrie_t *)
+                                                * it.node->_count));
+                                    alloc_assert (it.node->_next.table);
+
+                                    memmove (it.node->_next.table,
+                                             old_table
+                                               + (it.new_min - it.node->_min),
+                                             sizeof (generic_mtrie_t *)
+                                               * it.node->_count);
+                                    free (old_table);
+
+                                    it.node->_min = it.new_min;
+                                }
+                        }
+                    }
             }
+        }
     }
-}
-template <typename T>
-typename generic_mtrie_t<T>::rm_result
-generic_mtrie_t<T>::rm (prefix_t prefix_, size_t size_, value_t *pipe_)
-{
-    return rm_helper (prefix_, size_, pipe_);
+
+    free (buff);
 }

 template <typename T>
 typename generic_mtrie_t<T>::rm_result
-generic_mtrie_t<T>::rm_helper (prefix_t prefix_, size_t size_, value_t *pipe_)
+generic_mtrie_t<T>::rm (prefix_t prefix_, size_t size_, value_t *pipe_)
 {
-    if (!size_) {
-        if (!_pipes)
-            return not_found;
-
-        typename pipes_t::size_type erased = _pipes->erase (pipe_);
-        if (_pipes->empty ()) {
-            zmq_assert (erased == 1);
-            LIBZMQ_DELETE (_pipes);
-            return last_value_removed;
-        }
-        return (erased == 1) ? values_remain : not_found;
-    }
-
-    const unsigned char c = *prefix_;
-    if (!_count || c < _min || c >= _min + _count)
-        return not_found;
-
-    generic_mtrie_t *next_node =
-      _count == 1 ? _next.node : _next.table[c - _min];
-
-    if (!next_node)
-        return not_found;
+    //  This used to be implemented as a non-tail recursive travesal of the trie,
+    //  which means remote clients controlled the depth of the recursion and the
+    //  stack size.
+    //  To simulate the non-tail recursion, with post-recursion changes depending on
+    //  the result of the recursive call, a stack is used to re-visit the same node
+    //  and operate on it again after children have been visisted.
+    //  A boolean is used to record whether the node had already been visited and to
+    //  determine if the pre- or post- children visit actions have to be taken.
+    rm_result ret = not_found;
+    std::list<struct iter> stack;
+    struct iter it = {this, NULL, prefix_, size_, 0, 0, 0, false};
+    stack.push_back (it);
+
+    while (!stack.empty ()) {
+        it = stack.back ();
+        stack.pop_back ();
+
+        if (!it.processed_for_removal) {
+            if (!it.size) {
+                if (!it.node->_pipes) {
+                    ret = not_found;
+                    continue;
+                }
+
+                typename pipes_t::size_type erased =
+                  it.node->_pipes->erase (pipe_);
+                if (it.node->_pipes->empty ()) {
+                    zmq_assert (erased == 1);
+                    LIBZMQ_DELETE (it.node->_pipes);
+                    ret = last_value_removed;
+                    continue;
+                }
+
+                ret = (erased == 1) ? values_remain : not_found;
+                continue;
+            }

-    const rm_result ret = next_node->rm_helper (prefix_ + 1, size_ - 1, pipe_);
+            it.current_child = *it.prefix;
+            if (!it.node->_count || it.current_child < it.node->_min
+                || it.current_child >= it.node->_min + it.node->_count) {
+                ret = not_found;
+                continue;
+            }

-    if (next_node->is_redundant ()) {
-        LIBZMQ_DELETE (next_node);
-        zmq_assert (_count > 0);
+            it.next_node =
+              it.node->_count == 1
+                ? it.node->_next.node
+                : it.node->_next.table[it.current_child - it.node->_min];
+            if (!it.next_node) {
+                ret = not_found;
+                continue;
+            }

-        if (_count == 1) {
-            _next.node = 0;
-            _count = 0;
-            --_live_nodes;
-            zmq_assert (_live_nodes == 0);
+            it.processed_for_removal = true;
+            stack.push_back (it);
+            struct iter next = {
+              it.next_node, NULL, it.prefix + 1, it.size - 1, 0, 0, 0, false};
+            stack.push_back (next);
         } else {
-            _next.table[c - _min] = 0;
-            zmq_assert (_live_nodes > 1);
-            --_live_nodes;
-
-            //  Compact the table if possible
-            if (_live_nodes == 1) {
-                //  If there's only one live node in the table we can
-                //  switch to using the more compact single-node
-                //  representation
-                unsigned short i;
-                for (i = 0; i < _count; ++i)
-                    if (_next.table[i])
-                        break;
-
-                zmq_assert (i < _count);
-                _min += i;
-                _count = 1;
-                generic_mtrie_t *oldp = _next.table[i];
-                free (_next.table);
-                _next.node = oldp;
-            } else if (c == _min) {
-                //  We can compact the table "from the left"
-                unsigned short i;
-                for (i = 1; i < _count; ++i)
-                    if (_next.table[i])
-                        break;
-
-                zmq_assert (i < _count);
-                _min += i;
-                _count -= i;
-                generic_mtrie_t **old_table = _next.table;
-                _next.table = static_cast<generic_mtrie_t **> (
-                  malloc (sizeof (generic_mtrie_t *) * _count));
-                alloc_assert (_next.table);
-                memmove (_next.table, old_table + i,
-                         sizeof (generic_mtrie_t *) * _count);
-                free (old_table);
-            } else if (c == _min + _count - 1) {
-                //  We can compact the table "from the right"
-                unsigned short i;
-                for (i = 1; i < _count; ++i)
-                    if (_next.table[_count - 1 - i])
-                        break;
-
-                zmq_assert (i < _count);
-                _count -= i;
-                generic_mtrie_t **old_table = _next.table;
-                _next.table = static_cast<generic_mtrie_t **> (
-                  malloc (sizeof (generic_mtrie_t *) * _count));
-                alloc_assert (_next.table);
-                memmove (_next.table, old_table,
-                         sizeof (generic_mtrie_t *) * _count);
-                free (old_table);
+            it.processed_for_removal = false;
+
+            if (it.next_node->is_redundant ()) {
+                LIBZMQ_DELETE (it.next_node);
+                zmq_assert (it.node->_count > 0);
+
+                if (it.node->_count == 1) {
+                    it.node->_next.node = NULL;
+                    it.node->_count = 0;
+                    --it.node->_live_nodes;
+                    zmq_assert (it.node->_live_nodes == 0);
+                } else {
+                    it.node->_next.table[it.current_child - it.node->_min] = 0;
+                    zmq_assert (it.node->_live_nodes > 1);
+                    --it.node->_live_nodes;
+
+                    //  Compact the table if possible
+                    if (it.node->_live_nodes == 1) {
+                        //  If there's only one live node in the table we can
+                        //  switch to using the more compact single-node
+                        //  representation
+                        unsigned short i;
+                        for (i = 0; i < it.node->_count; ++i)
+                            if (it.node->_next.table[i])
+                                break;
+
+                        zmq_assert (i < it.node->_count);
+                        it.node->_min += i;
+                        it.node->_count = 1;
+                        generic_mtrie_t *oldp = it.node->_next.table[i];
+                        free (it.node->_next.table);
+                        it.node->_next.table = NULL;
+                        it.node->_next.node = oldp;
+                    } else if (it.current_child == it.node->_min) {
+                        //  We can compact the table "from the left"
+                        unsigned short i;
+                        for (i = 1; i < it.node->_count; ++i)
+                            if (it.node->_next.table[i])
+                                break;
+
+                        zmq_assert (i < it.node->_count);
+                        it.node->_min += i;
+                        it.node->_count -= i;
+                        generic_mtrie_t **old_table = it.node->_next.table;
+                        it.node->_next.table =
+                          static_cast<generic_mtrie_t **> (malloc (
+                            sizeof (generic_mtrie_t *) * it.node->_count));
+                        alloc_assert (it.node->_next.table);
+                        memmove (it.node->_next.table, old_table + i,
+                                 sizeof (generic_mtrie_t *) * it.node->_count);
+                        free (old_table);
+                    } else if (it.current_child
+                               == it.node->_min + it.node->_count - 1) {
+                        //  We can compact the table "from the right"
+                        unsigned short i;
+                        for (i = 1; i < it.node->_count; ++i)
+                            if (it.node->_next.table[it.node->_count - 1 - i])
+                                break;
+
+                        zmq_assert (i < it.node->_count);
+                        it.node->_count -= i;
+                        generic_mtrie_t **old_table = it.node->_next.table;
+                        it.node->_next.table =
+                          static_cast<generic_mtrie_t **> (malloc (
+                            sizeof (generic_mtrie_t *) * it.node->_count));
+                        alloc_assert (it.node->_next.table);
+                        memmove (it.node->_next.table, old_table,
+                                 sizeof (generic_mtrie_t *) * it.node->_count);
+                        free (old_table);
+                    }
+                }
             }
         }
     }


Merge pull request from GHSA-jjq6-mh2h-g39h

@@ -37,7 +37,7 @@ HeaderTableRecord::HeaderTableRecord(const char *n, HdrType theId, HdrFieldType
 const HeaderTableRecord&
 HeaderLookupTable_t::lookup (const char *buf, const std::size_t len) const {
     const HeaderTableRecord *r = HttpHeaderHashTable::lookup(buf, len);
-    if (!r)
+    if (!r || r->id == Http::HdrType::OTHER)
         return BadHdr;
     return *r;
 }


Fix sending malformed pong messages in some cases.

@@ -83,7 +83,7 @@ class CommandPing : public SplitCommand
 			return CMD_FAILURE;
 		}

-		ClientProtocol::Messages::Pong pong(parameters[0], origin ? parameters[1] : "");
+		ClientProtocol::Messages::Pong pong(parameters[0], origin ? parameters[1] : ServerInstance->Config->GetServerName());
 		user->Send(ServerInstance->GetRFCEvents().pong, pong);
 		return CMD_SUCCESS;
 	}


mgr/dashboard: Use secure cookies to store JWT Token

This PR intends to store the jwt token in secure cookies instead of local storage

Fixes: https://tracker.ceph.com/issues/44591

Signed-off-by: Aashish Sharma <aasharma@redhat.com>
Signed-off-by: Avan Thakkar <athakkar@redhat.com>
(cherry picked from commit 36703c63381e6723fff57266235f8230e6af1d92)

@@ -67,12 +67,20 @@ def decode_token(cls, token):

     @classmethod
     def get_token_from_header(cls):
-        auth_header = cherrypy.request.headers.get('authorization')
-        if auth_header is not None:
-            scheme, params = auth_header.split(' ', 1)
-            if scheme.lower() == 'bearer':
-                return params
-        return None
+        auth_cookie_name = 'token'
+        try:
+            # use cookie
+            return cherrypy.request.cookie[auth_cookie_name].value
+        except KeyError:
+            try:
+                # fall-back: use Authorization header
+                auth_header = cherrypy.request.headers.get('authorization')
+                if auth_header is not None:
+                    scheme, params = auth_header.split(' ', 1)
+                    if scheme.lower() == 'bearer':
+                        return params
+            except IndexError:
+                return None

     @classmethod
     def set_user(cls, username):


backport to 1.17: http: Fixing empty metadata map handling

Commit Message: Fixing a crash when the decoder receives an empty metadata map.

Additional Description:
Upon receiving an empty metadata map and trying to decode it an assertion is triggered in debug mode, and a seg-fault occurs in release mode.
The proposed fix ignores the empty metadata maps and updates a stats if one is received.

Risk Level: Medium for Envoy's running with Metadata support.
Testing: Added integration tests.
Docs Changes: Added a codec stats counter description.
Release Notes: Added bug fix description.
Platform Specific Features: N/A.
Fixes a fuzz bug: 25303

Signed-off-by: Adi Suissa-Peleg <adip@google.com>
Signed-off-by: Tony Allen <tony@allen.gg>

@@ -248,7 +248,7 @@ Http2Frame Http2Frame::makeWindowUpdateFrame(uint32_t stream_index, uint32_t inc

 // Note: encoder in codebase persists multiple maps, with each map representing an individual frame.
 Http2Frame Http2Frame::makeMetadataFrameFromMetadataMap(uint32_t stream_index,
-                                                        MetadataMap& metadata_map,
+                                                        const MetadataMap& metadata_map,
                                                         MetadataFlags flags) {
   const int numberOfNameValuePairs = metadata_map.size();
   absl::FixedArray<nghttp2_nv> nameValues(numberOfNameValuePairs);


Fix a dangerous integer overflow and a malloc of negative size.

PiperOrigin-RevId: 371254154
Change-Id: I250a98a3df26328770167025670235a963a72da0

@@ -173,6 +173,7 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {

   // Resize output tensor.
   TfLiteIntArray* output_shape = TfLiteIntArrayCreate(output_rank);
+  TF_LITE_ENSURE(context, output_shape != nullptr);
   int k = 0;
   int embedding_size = 1;
   int lookup_size = 1;


Prevent division by 0.

PiperOrigin-RevId: 370962554
Change-Id: I0b9b62f4d8e1046dd88f9433f8dfeaf61a901680

@@ -545,6 +545,7 @@ TfLiteStatus Prepare(KernelType kernel_type, TfLiteContext* context,
     // Only one scale factor per batch is typically necessary. See optimized
     // implementation for why we need to allocate for the height of the inputs
     // flattened to 2D.
+    TF_LITE_ENSURE(context, channels_in != 0);
     const int height = NumElements(input) / channels_in;
     int scaling_dims[1] = {height};
     if (!TfLiteIntArrayEqualsArray(scaling_factors->dims, 1, scaling_dims)) {
@@ -587,6 +588,7 @@ TfLiteStatus Prepare(KernelType kernel_type, TfLiteContext* context,
       input_offsets->type = kTfLiteInt32;
       input_offsets->allocation_type = kTfLiteArenaRw;
       // See above comment for the need to allocate for height of inputs.
+      TF_LITE_ENSURE(context, channels_in != 0);
       const int height = NumElements(input) / channels_in;
       const int input_offset_dims[1] = {height};
       if (!TfLiteIntArrayEqualsArray(input_offsets->dims, 1,
@@ -886,8 +888,9 @@ TfLiteStatus EvalHybridPerChannel(TfLiteContext* context, TfLiteNode* node,
   CalculateActivationRange(params->activation, &output_activation_min,
                            &output_activation_max);

-  const int input_size = NumElements(input) / SizeOfDimension(input, 0);
   const int batch_size = SizeOfDimension(input, 0);
+  TF_LITE_ENSURE(context, batch_size != 0);
+  const int input_size = NumElements(input) / batch_size;
   TfLiteTensor* quantized_input_tensor;
   TF_LITE_ENSURE_OK(context,
                     GetTemporarySafe(context, node, data->input_quantized_index,
@@ -989,8 +992,9 @@ TfLiteStatus EvalHybrid(TfLiteContext* context, TfLiteNode* node,
   CalculateActivationRange(params->activation, &output_activation_min,
                            &output_activation_max);

-  const int input_size = NumElements(input) / SizeOfDimension(input, 0);
   const int batch_size = SizeOfDimension(input, 0);
+  TF_LITE_ENSURE(context, batch_size != 0);
+  const int input_size = NumElements(input) / batch_size;

   const float* input_ptr = GetTensorData<float>(input);
   TfLiteTensor* quantized_input_tensor;


Fix another division by 0 in TFLite

PiperOrigin-RevId: 370800181
Change-Id: I924809166a6131f5075e6d45c455106538d755f9

@@ -591,6 +591,10 @@ TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) {
   const auto* params =
       reinterpret_cast<TfLiteTransposeConvParams*>(node->builtin_data);

+  // Prevent divisions by 0
+  TF_LITE_ENSURE(context, params->stride_height > 0);
+  TF_LITE_ENSURE(context, params->stride_width > 0);
+
   // Resize any deferred dynamic tensors
   if (IsDynamicTensor(output)) {
     TF_LITE_ENSURE_OK(context, ResizeTensor(context, output_shape, output));


5.6.1.4: Use in-memory archive instead of file IO

@@ -0,0 +1,62 @@
+ArcMemory::ArcMemory()
+{
+  Loaded=false;
+  SeekPos=0;
+}
+
+
+void ArcMemory::Load(const byte *Data,size_t Size)
+{
+  ArcData.Alloc(Size);
+  memcpy(&ArcData[0],Data,Size);
+  Loaded=true;
+  SeekPos=0;
+}
+
+
+bool ArcMemory::Unload()
+{
+  if (!Loaded)
+    return false;
+  Loaded=false;
+  return true;
+}
+
+
+bool ArcMemory::Read(void *Data,size_t Size,size_t &Result)
+{
+  if (!Loaded)
+    return false;
+  Result=(size_t)Min(Size,ArcData.Size()-SeekPos);
+  memcpy(Data,&ArcData[(size_t)SeekPos],Result);
+  SeekPos+=Result;
+  return true;
+}
+
+
+bool ArcMemory::Seek(int64 Offset,int Method)
+{
+  if (!Loaded)
+    return false;
+  if (Method==SEEK_SET)
+    SeekPos=Min(Offset,ArcData.Size());
+  else
+    if (Method==SEEK_CUR || Method==SEEK_END)
+    {
+      if (Method==SEEK_END)
+        SeekPos=ArcData.Size();
+      SeekPos+=(uint64)Offset;
+      if (SeekPos>ArcData.Size())
+        SeekPos=Offset<0 ? 0 : ArcData.Size();
+    }
+  return true;
+}
+
+
+bool ArcMemory::Tell(int64 *Pos)
+{
+  if (!Loaded)
+    return false;
+  *Pos=SeekPos;
+  return true;
+}


@@ -24,6 +24,7 @@
 #ifndef SFX_MODULE
 #define USE_QOPEN
 #endif
+#define USE_ARCMEM

 // Produce the value, which is equal or larger than 'v' and aligned to 'a'.
 #define ALIGN_VALUE(v,a) (size_t(v) + ( (~size_t(v) + 1) & (a - 1) ) )


Handle more Range requests (#790)

Also removed some effectively unused code.

@@ -78,7 +78,6 @@ class HttpHdrRange
     int64_t firstOffset() const;
     int64_t lowestOffset(int64_t) const;
     bool offsetLimitExceeded(const int64_t limit) const;
-    bool contains(const HttpHdrRangeSpec& r) const;
     std::vector<HttpHdrRangeSpec *> specs;

 private:
@@ -100,9 +99,9 @@ class HttpHdrRangeIter
     void updateSpec();
     int64_t debt() const;
     void debt(int64_t);
-    int64_t debt_size;      /* bytes left to send from the current spec */
+    int64_t debt_size = 0;  /* bytes left to send from the current spec */
     String boundary;        /* boundary for multipart responses */
-    bool valid;
+    bool valid = false;
 };

 #endif /* SQUID_HTTPHEADERRANGE_H */


force fraction to a limited resolution to finally solve those pesky numerical edge cases

@@ -40,54 +40,20 @@ heif::Error heif::Error::Ok(heif_error_Ok);

-static int32_t gcd(int a, int b)
-{
-  if (a == 0 && b == 0) {
-    return 1;
-  }
-
-  if (a == 0) return b;
-  if (b == 0) return a;
-
-  int32_t h;
-
-  do {
-    h = a % b;
-    a = b;
-    b = h;
-  } while (b != 0);
-
-  return a;
-}
-
-
 Fraction::Fraction(int32_t num,int32_t den)
 {
-  int32_t g = gcd(num, den);
-
-  // these strange tests are for catching the case that we divide -2147483648 by -1,
-  // which would exceed the maximum positive value by one.
-
-  if (num == std::numeric_limits<int32_t>::min() && g == -1) {
-    num++;
-  }
-
-  if (den == std::numeric_limits<int32_t>::min() && g == -1) {
-    den++;
-  }
-
-
-  numerator = num / g;
-  denominator = den / g;
-
-
   // Reduce resolution of fraction until we are in a safe range.
   // We need this as adding fractions may lead to very large denominators
   // (e.g. 0x10000 * 0x10000 > 0x100000000 -> overflow, leading to integer 0)

-  while (denominator > MAX_FRACTION_DENOMINATOR) {
-    numerator >>= 1;
-    denominator >>= 1;
+  while (denominator > MAX_FRACTION_VALUE || denominator < -MAX_FRACTION_VALUE) {
+    numerator /= 2;
+    denominator /= 2;
+  }
+
+  while (numerator > MAX_FRACTION_VALUE || numerator < -MAX_FRACTION_VALUE) {
+    numerator /= 2;
+    denominator /= 2;
   }
 }


Add sysno check in MessageReader

The sysno in MessageReader is interpreted from the Message header passed
from the host. A malicious Message header may provide a modified sysno
to bypass the validation, and overwrites enclave memory. This change
adds a check for sysno to make sure it matches the expected value.

This issue was reported by Qinkun Bao, Zhaofeng Chen, Mingshen Sun, and
Kang Li from Baidu Security.

PiperOrigin-RevId: 377328054
Change-Id: I3ff6f60694d3390f66da89d139cf7cc7b49abaea

@@ -115,6 +115,9 @@ extern "C" int64_t enc_untrusted_syscall(int sysno, ...) {
   // Copy outputs back into pointer parameters.
   auto response_reader =
       asylo::system_call::MessageReader({response_buffer, response_size});
+  if (response_reader.sysno() != sysno) {
+    error_handler("system_call.cc: Unexpected sysno in response");
+  }
   const asylo::primitives::PrimitiveStatus response_status =
       response_reader.Validate();
   if (!response_status.ok()) {


security fixes

https://hhvm.com/blog/2020/11/12/security-update.html

@@ -598,11 +598,11 @@ Variant HHVM_METHOD(XMLReader, expand,
   if (!basenode.isNull()) {
     auto dombasenode = Native::data<DOMNode>(basenode.toObject());
     doc = dombasenode->doc();
-    docp = doc->docp();
-    if (docp == nullptr) {
+    if (doc == nullptr || doc->docp() == nullptr) {
       raise_warning("Invalid State Error");
       return false;
     }
+    docp = doc->docp();
   }

   if (data->m_ptr) {


Remove X-Endpoint-API-UserInfo for all header occurrences. (#845)

@@ -458,7 +458,7 @@ void CheckAuthTest::TestValidToken(const std::string &auth_token,
       }));
   std::cout << "need be replaced: " << user_info << std::endl;
   EXPECT_CALL(*raw_request_,
-              AddHeaderToBackend(kEndpointApiUserInfo, user_info))
+              AddHeaderToBackend(kEndpointApiUserInfo, user_info, false))
       .WillOnce(Return(utils::Status::OK));

   CheckAuth(context_, [](Status status) { ASSERT_TRUE(status.ok()); });
@@ -490,8 +490,8 @@ TEST_F(CheckAuthTest, TestOKAuth) {
       }));
   EXPECT_CALL(*raw_request_, SetAuthToken(kToken)).Times(1);
   EXPECT_CALL(*raw_env_, DoRunHTTPRequest(_)).Times(0);
-  EXPECT_CALL(*raw_request_,
-              AddHeaderToBackend(kEndpointApiUserInfo, kUserInfo_kSub_kIss))
+  EXPECT_CALL(*raw_request_, AddHeaderToBackend(kEndpointApiUserInfo,
+                                                kUserInfo_kSub_kIss, false))
       .WillOnce(Return(utils::Status::OK));

   CheckAuth(context_, [](Status status) { ASSERT_TRUE(status.ok()); });
@@ -513,8 +513,8 @@ TEST_F(CheckAuthTest, TestOKAuth) {
       }));
   EXPECT_CALL(*raw_request_, SetAuthToken(kToken2)).Times(1);
   EXPECT_CALL(*raw_env_, DoRunHTTPRequest(_)).Times(0);
-  EXPECT_CALL(*raw_request_,
-              AddHeaderToBackend(kEndpointApiUserInfo, kUserInfo_kSub2_kIss2))
+  EXPECT_CALL(*raw_request_, AddHeaderToBackend(kEndpointApiUserInfo,
+                                                kUserInfo_kSub2_kIss2, false))
       .WillOnce(Return(utils::Status::OK));

   CheckAuth(context_, [](Status status) { ASSERT_TRUE(status.ok()); });
@@ -605,8 +605,8 @@ TEST_F(CheckAuthTest, TestNoOpenId) {
         std::map<std::string, std::string> empty;
         req->OnComplete(Status::OK, std::move(empty), std::move(body));
       }));
-  EXPECT_CALL(*raw_request_,
-              AddHeaderToBackend(kEndpointApiUserInfo, kUserInfo_kSub_kIss2))
+  EXPECT_CALL(*raw_request_, AddHeaderToBackend(kEndpointApiUserInfo,
+                                                kUserInfo_kSub_kIss2, false))
       .WillOnce(Return(utils::Status::OK));

   CheckAuth(context_, [](Status status) { ASSERT_TRUE(status.ok()); });


Merge pull request #20306 from fuzzard/fix_20305

[Playlist] dont use istream directly to a tinyxml structure

@@ -289,8 +289,9 @@ bool CPlayListASX::LoadData(std::istream& stream)
   }
   else
   {
+    std::string asxstream(std::istreambuf_iterator<char>(stream), {});
     CXBMCTinyXML xmlDoc;
-    stream >> xmlDoc;
+    xmlDoc.Parse(asxstream, TIXML_DEFAULT_ENCODING);

     if (xmlDoc.Error())
     {
@@ -300,6 +301,9 @@ bool CPlayListASX::LoadData(std::istream& stream)

     TiXmlElement *pRootElement = xmlDoc.RootElement();

+    if (!pRootElement)
+      return false;
+
     // lowercase every element
     TiXmlNode *pNode = pRootElement;
     TiXmlNode *pChild = NULL;


CVE-2022-23606

Avoid closing other connections to prevent deep recursion when a large number of idle connections are closed at the start of a pool drain, when a connection is closed.

Signed-off-by: Yan Avlasov <yavlasov@google.com>

@@ -1,6 +1,7 @@
 #include "source/common/conn_pool/conn_pool_base.h"

 #include "source/common/common/assert.h"
+#include "source/common/common/debug_recursion_checker.h"
 #include "source/common/network/transport_socket_options_impl.h"
 #include "source/common/runtime/runtime_features.h"
 #include "source/common/stats/timespan_impl.h"
@@ -349,6 +350,8 @@ void ConnPoolImplBase::transitionActiveClientState(ActiveClient& client,
 void ConnPoolImplBase::addIdleCallbackImpl(Instance::IdleCb cb) { idle_callbacks_.push_back(cb); }

 void ConnPoolImplBase::closeIdleConnectionsForDrainingPool() {
+  Common::AutoDebugRecursionChecker assert_not_in(recursion_checker_);
+
   // Create a separate list of elements to close to avoid mutate-while-iterating problems.
   std::list<ActiveClient*> to_close;

@@ -403,11 +406,7 @@ bool ConnPoolImplBase::isIdleImpl() const {
          connecting_clients_.empty();
 }

-void ConnPoolImplBase::checkForIdleAndCloseIdleConnsIfDraining() {
-  if (is_draining_for_deletion_) {
-    closeIdleConnectionsForDrainingPool();
-  }
-
+void ConnPoolImplBase::checkForIdleAndNotify() {
   if (isIdleImpl()) {
     ENVOY_LOG(debug, "invoking idle callbacks - is_draining_for_deletion_={}",
               is_draining_for_deletion_);
@@ -417,6 +416,14 @@ void ConnPoolImplBase::checkForIdleAndCloseIdleConnsIfDraining() {
   }
 }

+void ConnPoolImplBase::checkForIdleAndCloseIdleConnsIfDraining() {
+  if (is_draining_for_deletion_) {
+    closeIdleConnectionsForDrainingPool();
+  }
+
+  checkForIdleAndNotify();
+}
+
 void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view failure_reason,
                                          Network::ConnectionEvent event) {
   if (client.state() == ActiveClient::State::CONNECTING) {
@@ -487,7 +494,15 @@ void ConnPoolImplBase::onConnectionEvent(ActiveClient& client, absl::string_view

     dispatcher_.deferredDelete(client.removeFromList(owningList(client.state())));

-    checkForIdleAndCloseIdleConnsIfDraining();
+    // Check if the pool transitioned to idle state after removing closed client
+    // from one of the client tracking lists.
+    // There is no need to check if other connections are idle in a draining pool
+    // because the pool will close all idle connection when it is starting to
+    // drain.
+    // Trying to close other connections here can lead to deep recursion when
+    // a large number idle connections are closed at the start of pool drain.
+    // See CdsIntegrationTest.CdsClusterDownWithLotsOfIdleConnections for an example.
+    checkForIdleAndNotify();

     client.setState(ActiveClient::State::CLOSED);


CVE-2021-43826

Signed-off-by: Yan Avlasov <yavlasov@google.com>

@@ -816,6 +816,69 @@ TEST_P(TcpTunnelingIntegrationTest, ResetStreamTest) {
   tcp_client_->waitForDisconnect();
 }

+TEST_P(TcpTunnelingIntegrationTest, UpstreamConnectingDownstreamDisconnect) {
+  if (upstreamProtocol() == Http::CodecType::HTTP1) {
+    return;
+  }
+
+#if defined(WIN32)
+  // TODO(ggreenway): figure out why this test fails on Windows and remove this disable.
+  // Failing tests:
+  // IpAndHttpVersions/TcpTunnelingIntegrationTest.UpstreamConnectingDownstreamDisconnect/IPv4_HttpDownstream_Http3UpstreamBareHttp2,
+  // IpAndHttpVersions/TcpTunnelingIntegrationTest.UpstreamConnectingDownstreamDisconnect/IPv6_HttpDownstream_Http2UpstreamWrappedHttp2,
+  // Times out at the end of the test on `ASSERT_TRUE(upstream_request_->waitForReset());`.
+  return;
+#endif
+
+  config_helper_.addConfigModifier([&](envoy::config::bootstrap::v3::Bootstrap& bootstrap) -> void {
+    envoy::extensions::filters::network::tcp_proxy::v3::TcpProxy proxy_config;
+    proxy_config.set_stat_prefix("tcp_stats");
+    proxy_config.set_cluster("cluster_0");
+    proxy_config.mutable_tunneling_config()->set_hostname("host.com:80");
+
+    // Enable retries. The crash is due to retrying after the downstream connection is closed, which
+    // can't occur if retries are not enabled.
+    proxy_config.mutable_max_connect_attempts()->set_value(2);
+
+    auto* listeners = bootstrap.mutable_static_resources()->mutable_listeners();
+    for (auto& listener : *listeners) {
+      if (listener.name() != "tcp_proxy") {
+        continue;
+      }
+      auto* filter_chain = listener.mutable_filter_chains(0);
+      auto* filter = filter_chain->mutable_filters(0);
+      filter->mutable_typed_config()->PackFrom(proxy_config);
+
+      // Use TLS because it will respond to a TCP half-close during handshake by closing the
+      // connection.
+      envoy::extensions::transport_sockets::tls::v3::DownstreamTlsContext tls_context;
+      ConfigHelper::initializeTls({}, *tls_context.mutable_common_tls_context());
+      filter_chain->mutable_transport_socket()->set_name("envoy.transport_sockets.tls");
+      filter_chain->mutable_transport_socket()->mutable_typed_config()->PackFrom(tls_context);
+
+      break;
+    }
+  });
+
+  enableHalfClose(false);
+  initialize();
+
+  IntegrationTcpClientPtr tcp_client = makeTcpConnection(lookupPort("tcp_proxy"));
+
+  // Wait for the request for a connection, but don't send a response back yet. This ensures that
+  // tcp_proxy is stuck in `connecting_`.
+  ASSERT_TRUE(fake_upstreams_[0]->waitForHttpConnection(*dispatcher_, fake_upstream_connection_));
+  ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_));
+  ASSERT_TRUE(upstream_request_->waitForHeadersComplete());
+
+  // Close the client connection. The TLS transport socket will detect this even while
+  // `readDisable(true)` on the connection, and will raise a `RemoteClose` event.
+  tcp_client->close();
+
+  ASSERT_TRUE(upstream_request_->waitForReset());
+  ASSERT_TRUE(fake_upstream_connection_->close());
+}
+
 TEST_P(TcpTunnelingIntegrationTest, TestIdletimeoutWithLargeOutstandingData) {
   enableHalfClose(false);
   config_helper_.setBufferLimits(1024, 1024);


Specify type for matching Subject Alternative Name. (#18628)

Signed-off-by: Pradeep Rao <pcrao@google.com>

@@ -1,10 +1,13 @@
 #include "source/common/ssl/certificate_validation_context_config_impl.h"

 #include "envoy/common/exception.h"
+#include "envoy/config/core/v3/extension.pb.h"
 #include "envoy/extensions/transport_sockets/tls/v3/cert.pb.h"
+#include "envoy/extensions/transport_sockets/tls/v3/common.pb.h"

 #include "source/common/common/empty_string.h"
 #include "source/common/common/fmt.h"
+#include "source/common/common/logger.h"
 #include "source/common/config/datasource.h"

 namespace Envoy {
@@ -22,8 +25,7 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl(
       certificate_revocation_list_path_(
           Config::DataSource::getPath(config.crl())
               .value_or(certificate_revocation_list_.empty() ? EMPTY_STRING : INLINE_STRING)),
-      subject_alt_name_matchers_(config.match_subject_alt_names().begin(),
-                                 config.match_subject_alt_names().end()),
+      subject_alt_name_matchers_(getSubjectAltNameMatchers(config)),
       verify_certificate_hash_list_(config.verify_certificate_hash().begin(),
                                     config.verify_certificate_hash().end()),
       verify_certificate_spki_list_(config.verify_certificate_spki().begin(),
@@ -51,5 +53,34 @@ CertificateValidationContextConfigImpl::CertificateValidationContextConfigImpl(
   }
 }

+std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+CertificateValidationContextConfigImpl::getSubjectAltNameMatchers(
+    const envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext& config) {
+  if (!config.match_typed_subject_alt_names().empty() &&
+      !config.match_subject_alt_names().empty()) {
+    throw EnvoyException("SAN-based verification using both match_typed_subject_alt_names and "
+                         "the deprecated match_subject_alt_names is not allowed");
+  }
+  std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+      subject_alt_name_matchers(config.match_typed_subject_alt_names().begin(),
+                                config.match_typed_subject_alt_names().end());
+  // Handle deprecated string type san matchers without san type specified, by
+  // creating a matcher for each supported type.
+  for (const envoy::type::matcher::v3::StringMatcher& matcher : config.match_subject_alt_names()) {
+    static constexpr std::array<
+        envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::SanType, 4>
+        san_types{envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
+                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::URI,
+                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::EMAIL,
+                  envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::IP_ADDRESS};
+    for (const auto san_type : san_types) {
+      subject_alt_name_matchers.emplace_back();
+      subject_alt_name_matchers.back().set_san_type(san_type);
+      *subject_alt_name_matchers.back().mutable_matcher() = matcher;
+    }
+  }
+  return subject_alt_name_matchers;
+}
+
 } // namespace Ssl
 } // namespace Envoy


@@ -665,7 +665,10 @@ TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) {
   dynamic_cvc->set_allow_expired_certificate(false);
   dynamic_cvc->mutable_trusted_ca()->set_filename(TestEnvironment::substitute(
       "{{ test_rundir }}/test/extensions/transport_sockets/tls/test_data/ca_cert.pem"));
-  dynamic_cvc->add_match_subject_alt_names()->set_exact("second san");
+  auto* san_matcher = dynamic_cvc->add_match_typed_subject_alt_names();
+  san_matcher->mutable_matcher()->set_exact("second san");
+  san_matcher->set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
   const std::string dynamic_verify_certificate_spki =
       "QGJRPdmx/r5EGOFLb2MTiZp2isyC0Whht7iazhzXaCM=";
   dynamic_cvc->add_verify_certificate_spki(dynamic_verify_certificate_spki);
@@ -681,7 +684,10 @@ TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) {
   envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext default_cvc;
   default_cvc.set_allow_expired_certificate(true);
   default_cvc.mutable_trusted_ca()->set_inline_bytes("fake trusted ca");
-  default_cvc.add_match_subject_alt_names()->set_exact("first san");
+  san_matcher = default_cvc.add_match_typed_subject_alt_names();
+  san_matcher->mutable_matcher()->set_exact("first san");
+  san_matcher->set_san_type(
+      envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS);
   default_cvc.add_verify_certificate_hash(default_verify_certificate_hash);
   envoy::extensions::transport_sockets::tls::v3::CertificateValidationContext merged_cvc =
       default_cvc;
@@ -697,8 +703,12 @@ TEST_F(SdsApiTest, DefaultCertificateValidationContextTest) {
             cvc_config.caCert());
   // Verify that repeated fields are concatenated.
   EXPECT_EQ(2, cvc_config.subjectAltNameMatchers().size());
-  EXPECT_EQ("first san", cvc_config.subjectAltNameMatchers()[0].exact());
-  EXPECT_EQ("second san", cvc_config.subjectAltNameMatchers()[1].exact());
+  EXPECT_EQ("first san", cvc_config.subjectAltNameMatchers()[0].matcher().exact());
+  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
+            cvc_config.subjectAltNameMatchers()[0].san_type());
+  EXPECT_EQ("second san", cvc_config.subjectAltNameMatchers()[1].matcher().exact());
+  EXPECT_EQ(envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher::DNS,
+            cvc_config.subjectAltNameMatchers()[1].san_type());
   // Verify that if dynamic CertificateValidationContext does not set certificate hash list, the new
   // secret contains hash list from default CertificateValidationContext.
   EXPECT_EQ(1, cvc_config.verifyCertificateHashList().size());


@@ -33,7 +33,8 @@ class TestCertificateValidationContextConfig
 public:
   TestCertificateValidationContextConfig(
       envoy::config::core::v3::TypedExtensionConfig config, bool allow_expired_certificate = false,
-      std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers = {})
+      std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+          san_matchers = {})
       : allow_expired_certificate_(allow_expired_certificate), api_(Api::createApiForTest()),
         custom_validator_config_(config), san_matchers_(san_matchers){};
   TestCertificateValidationContextConfig()
@@ -47,7 +48,7 @@ class TestCertificateValidationContextConfig
   const std::string& certificateRevocationListPath() const final {
     CONSTRUCT_ON_FIRST_USE(std::string, "");
   }
-  const std::vector<envoy::type::matcher::v3::StringMatcher>&
+  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>&
   subjectAltNameMatchers() const override {
     return san_matchers_;
   }
@@ -78,7 +79,8 @@ class TestCertificateValidationContextConfig
   bool allow_expired_certificate_{false};
   Api::ApiPtr api_;
   const absl::optional<envoy::config::core::v3::TypedExtensionConfig> custom_validator_config_;
-  const std::vector<envoy::type::matcher::v3::StringMatcher> san_matchers_{};
+  const std::vector<envoy::extensions::transport_sockets::tls::v3::SubjectAltNameMatcher>
+      san_matchers_{};
 };

 } // namespace Tls


CVE-2022-21654

tls allows re-use when some cert validation settings have changed

Signed-off-by: Yan Avlasov <yavlasov@google.com>

@@ -378,6 +378,35 @@ void DefaultCertValidator::updateDigestForSessionId(bssl::ScopedEVP_MD_CTX& md,
                               sizeof(std::remove_reference<decltype(hash)>::type::value_type));
     RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or(""));
   }
+
+  rc = EVP_DigestUpdate(md.get(), &verify_trusted_ca_, sizeof(verify_trusted_ca_));
+  RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or(""));
+
+  if (config_ != nullptr) {
+    for (const auto& matcher : config_->subjectAltNameMatchers()) {
+      size_t hash = MessageUtil::hash(matcher);
+      rc = EVP_DigestUpdate(md.get(), &hash, sizeof(hash));
+      RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or(""));
+    }
+
+    const std::string& crl = config_->certificateRevocationList();
+    if (!crl.empty()) {
+      rc = EVP_DigestUpdate(md.get(), crl.data(), crl.length());
+      RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or(""));
+    }
+
+    bool allow_expired = config_->allowExpiredCertificate();
+    rc = EVP_DigestUpdate(md.get(), &allow_expired, sizeof(allow_expired));
+    RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or(""));
+
+    auto trust_chain_verification = config_->trustChainVerification();
+    rc = EVP_DigestUpdate(md.get(), &trust_chain_verification, sizeof(trust_chain_verification));
+    RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or(""));
+
+    auto only_leaf_crl = config_->onlyVerifyLeafCertificateCrl();
+    rc = EVP_DigestUpdate(md.get(), &only_leaf_crl, sizeof(only_leaf_crl));
+    RELEASE_ASSERT(rc == 1, Utility::getLastCryptoError().value_or(""));
+  }
 }

 void DefaultCertValidator::addClientValidationContext(SSL_CTX* ctx, bool require_client_cert) {


fix static file serving security issue; fix url path encoding issue

@@ -67,7 +67,7 @@ ViewPtr Router::FindView(const std::string& method, const std::string& url,
   return ViewPtr();
 }

-bool Router::MatchView(const std::string& method, const std::string& url,
+bool Router::MatchView(const std::string& method, const std::string& url_path,
                        bool* stream) {
   assert(stream != nullptr);
   *stream = false;
@@ -80,13 +80,12 @@ bool Router::MatchView(const std::string& method, const std::string& url,

     if (route.url.empty()) {
       std::smatch match;
-
-      if (std::regex_match(url, match, route.url_regex)) {
+      if (std::regex_match(url_path, match, route.url_regex)) {
         *stream = route.view->Stream(method);
         return true;
       }
     } else {
-      if (boost::iequals(route.url, url)) {
+      if (boost::iequals(route.url, url_path)) {
         *stream = route.view->Stream(method);
         return true;
       }


vt_kdsetmode: extend console locking

As per the long-suffering comment.

Reported-by: Minh Yuan <yuanmingbuaa@gmail.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Jiri Slaby <jirislaby@kernel.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

@@ -246,6 +246,8 @@ int vt_waitactive(int n)
  *
  * XXX It should at least call into the driver, fbdev's definitely need to
  * restore their engine state. --BenH
+ *
+ * Called with the console lock held.
  */
 static int vt_kdsetmode(struct vc_data *vc, unsigned long mode)
 {
@@ -262,7 +264,6 @@ static int vt_kdsetmode(struct vc_data *vc, unsigned long mode)
 		return -EINVAL;
 	}

-	/* FIXME: this needs the console lock extending */
 	if (vc->vc_mode == mode)
 		return 0;

@@ -271,12 +272,10 @@ static int vt_kdsetmode(struct vc_data *vc, unsigned long mode)
 		return 0;

 	/* explicitly blank/unblank the screen if switching modes */
-	console_lock();
 	if (mode == KD_TEXT)
 		do_unblank_screen(1);
 	else
 		do_blank_screen(1);
-	console_unlock();

 	return 0;
 }
@@ -378,7 +377,10 @@ static int vt_k_ioctl(struct tty_struct *tty, unsigned int cmd,
 		if (!perm)
 			return -EPERM;

-		return vt_kdsetmode(vc, arg);
+		console_lock();
+		ret = vt_kdsetmode(vc, arg);
+		console_unlock();
+		return ret;

 	case KDGETMODE:
 		return put_user(vc->vc_mode, (int __user *)arg);


- Modified TileManager.cc to verify that malloc() has correctly allocated memory.
- Updated numerical types to std::size_t in RawTile.h, TileManager.cc, KakaduImage.cc, OpenJPEG.cc and Transforms.cc when allocating memory via new to avoid integer overflow - fixes remaining problems identified in https://github.com/ruven/iipsrv/issues/223.

@@ -79,7 +79,7 @@ class RawTile{
   int memoryManaged;

   /// The size of the data pointed to by data
-  unsigned int dataLength;
+  size_t dataLength;

   /// The width in pixels of this tile
   unsigned int width;


Prevent memory leak in decoding PNG images.

PiperOrigin-RevId: 409300653
Change-Id: I6182124c545989cef80cefd439b659095920763b

@@ -18,6 +18,8 @@ limitations under the License.
 #include <cstdint>
 #include <memory>

+#include "tensorflow/core/lib/gtl/cleanup.h"
+
 #define EIGEN_USE_THREADS

 #include "absl/strings/escaping.h"
@@ -326,6 +328,16 @@ class DecodeImageV2Op : public OpKernel {
         context, png::CommonInitDecode(input, channels_, channel_bits, &decode),
         errors::InvalidArgument("Invalid PNG. Failed to initialize decoder."));

+    // If we reach this point, then there is data in `decode` which must be
+    // freed by the time we end execution in this function. We cannot call
+    // `png::CommonFreeDecode()` before an `OP_REQUIRES` because if
+    // `OP_REQUIRES` constraint is satisfied then the data would be freed
+    // prematurely. Instead, let's use a `Cleanup` object.
+    auto cleanup = gtl::MakeCleanup([&decode]() {
+      std::cerr << "Cleanup called...\n";
+      png::CommonFreeDecode(&decode);
+    });
+
     // Verify that width and height are not too large:
     // - verify width and height don't overflow int.
     // - width can later be multiplied by channels_ and sizeof(uint16), so


Prevent null dereference read in `GetInitOp`.

We have a map of maps. We test that the key exists in the first map but then we don't have any validation that this also means the second map has the needed key. In the scenarios where this is not the case, we'll dereference a nullptr, if we don't have this check

PiperOrigin-RevId: 408739325
Change-Id: If9bb7ed759aba1f3b56a34913f209508dbaf65ce

@@ -34,9 +34,14 @@ Status GetInitOp(const string& export_dir, const MetaGraphDef& meta_graph_def,
   const auto& init_op_sig_it =
       meta_graph_def.signature_def().find(kSavedModelInitOpSignatureKey);
   if (init_op_sig_it != sig_def_map.end()) {
-    *init_op_name = init_op_sig_it->second.outputs()
-                        .find(kSavedModelInitOpSignatureKey)
-                        ->second.name();
+    const auto& sig_def_outputs = init_op_sig_it->second.outputs();
+    const auto& sig_def_outputs_it =
+        sig_def_outputs.find(kSavedModelInitOpSignatureKey);
+    if (sig_def_outputs_it == sig_def_outputs.end()) {
+      return errors::FailedPrecondition("Could not find output ",
+                                        kSavedModelInitOpSignatureKey);
+    }
+    *init_op_name = sig_def_outputs_it->second.name();
     return Status::OK();
   }


Prevent `CHECK`-fail when decoding resource handles from proto

In certain scenarios, the proto might contain tensors that have too many elements (overflow). This is a `CHECK`-fail in general, but we should prevent this, given how many CVEs caused by that we have received this year (a large fraction of 200).

PiperOrigin-RevId: 408049766
Change-Id: I2ac20b247aa8ed9110846fbdb7a0a9401f2c168c

@@ -17,8 +17,11 @@ limitations under the License.

 #include "absl/strings/str_format.h"
 #include "tensorflow/core/framework/resource_handle.pb.h"
+#include "tensorflow/core/framework/tensor_shape.h"
 #include "tensorflow/core/lib/core/errors.h"
 #include "tensorflow/core/lib/strings/strcat.h"
+#include "tensorflow/core/platform/errors.h"
+#include "tensorflow/core/platform/macros.h"

 namespace tensorflow {

@@ -28,7 +31,15 @@ namespace tensorflow {
 ResourceHandle::ResourceHandle() {}

 ResourceHandle::ResourceHandle(const ResourceHandleProto& proto) {
-  FromProto(proto);
+  TF_CHECK_OK(FromProto(proto));
+}
+
+Status ResourceHandle::BuildResourceHandle(const ResourceHandleProto& proto,
+                                           ResourceHandle* out) {
+  if (out == nullptr)
+    return errors::Internal(
+        "BuildResourceHandle() was called with nullptr for the output");
+  return out->FromProto(proto);
 }

 ResourceHandle::~ResourceHandle() {}
@@ -46,7 +57,7 @@ void ResourceHandle::AsProto(ResourceHandleProto* proto) const {
   }
 }

-void ResourceHandle::FromProto(const ResourceHandleProto& proto) {
+Status ResourceHandle::FromProto(const ResourceHandleProto& proto) {
   set_device(proto.device());
   set_container(proto.container());
   set_name(proto.name());
@@ -55,10 +66,16 @@ void ResourceHandle::FromProto(const ResourceHandleProto& proto) {
   std::vector<DtypeAndPartialTensorShape> dtypes_and_shapes;
   for (const auto& dtype_and_shape : proto.dtypes_and_shapes()) {
     DataType dtype = dtype_and_shape.dtype();
-    PartialTensorShape shape(dtype_and_shape.shape());
+    PartialTensorShape shape;
+    Status s = PartialTensorShape::BuildPartialTensorShape(
+        dtype_and_shape.shape(), &shape);
+    if (!s.ok()) {
+      return s;
+    }
     dtypes_and_shapes.push_back(DtypeAndPartialTensorShape{dtype, shape});
   }
   dtypes_and_shapes_ = std::move(dtypes_and_shapes);
+  return Status::OK();
 }

 string ResourceHandle::SerializeAsString() const {
@@ -69,9 +86,7 @@ string ResourceHandle::SerializeAsString() const {

 bool ResourceHandle::ParseFromString(const string& s) {
   ResourceHandleProto proto;
-  const bool status = proto.ParseFromString(s);
-  if (status) FromProto(proto);
-  return status;
+  return proto.ParseFromString(s) && FromProto(proto).ok();
 }

 string ResourceHandle::DebugString() const {
@@ -140,7 +155,9 @@ bool DecodeResourceHandleList(std::unique_ptr<port::StringListDecoder> d,
     if (!proto.ParseFromArray(d->Data(sizes[i]), sizes[i])) {
       return false;
     }
-    ps[i].FromProto(proto);
+    if (!ps[i].FromProto(proto).ok()) {
+      return false;
+    }
   }
   return true;
 }


[lite] Move MultiplyAndCheckOverflow to util to be able to share it.

PiperOrigin-RevId: 416897229
Change-Id: I5feb44881bdcbb6ed911da4f17c55bb978754059

@@ -690,27 +690,6 @@ TfLiteStatus Subgraph::CheckInputAndOutputForOverlap(const int* input_indices,
   return kTfLiteOk;
 }

-namespace {
-// Multiply two sizes and return true if overflow occurred;
-// This is based off tensorflow/overflow.h but is simpler as we already
-// have unsigned numbers. It is also generalized to work where sizeof(size_t)
-// is not 8.
-TfLiteStatus MultiplyAndCheckOverflow(size_t a, size_t b, size_t* product) {
-  // Multiplying a * b where a and b are size_t cannot result in overflow in a
-  // size_t accumulator if both numbers have no non-zero bits in their upper
-  // half.
-  constexpr size_t size_t_bits = 8 * sizeof(size_t);
-  constexpr size_t overflow_upper_half_bit_position = size_t_bits / 2;
-  *product = a * b;
-  // If neither integers have non-zero bits past 32 bits can't overflow.
-  // Otherwise check using slow devision.
-  if (TFLITE_EXPECT_FALSE((a | b) >> overflow_upper_half_bit_position != 0)) {
-    if (a != 0 && *product / a != b) return kTfLiteError;
-  }
-  return kTfLiteOk;
-}
-}  // namespace
-
 TfLiteStatus Subgraph::BytesRequired(TfLiteType type, const int* dims,
                                      size_t dims_size, size_t* bytes) {
   TF_LITE_ENSURE(&context_, bytes != nullptr);


@@ -1318,7 +1318,7 @@ pspdf_prepare_outpages()
     chapter_outstarts[c] = num_outpages;

     for (i = chapter_starts[c], j = 0, nup = -1, page = pages + i;
-         i <= chapter_ends[c];
+         i <= chapter_ends[c] && num_outpages < num_pages;
 	 i ++, page ++)
     {
       if (nup != page->nup)


@@ -1404,6 +1404,8 @@ pspdf_prepare_page(int page)		/* I - Page number */

   DEBUG_printf(("pspdf_prepare_page(%d)\n", page));
+  if (page < 0 || page >= num_pages)
+    return;

  /*
   * Make a page number; use roman numerals for the table of contents
@@ -12285,6 +12287,9 @@ write_trailer(FILE  *out,		/* I - Output file */

       for (j = 1; j <= TocDocCount; j ++)
       {
+        if (chapter_starts[j] < 0)
+          continue;
+
         page  = pages + chapter_starts[j];
 	start = chapter_starts[j] - chapter_starts[1] + 1;
 	type  = 'D';


HTCONDOR-985: back-port check of LimitAuthorization in DaemonCommandProtocol

@@ -1355,11 +1355,41 @@ DaemonCommandProtocol::CommandProtocolResult DaemonCommandProtocol::VerifyComman
 			m_perm = USER_AUTH_FAILURE;
 		}
 		else {
-			m_perm = daemonCore->Verify(
-						  command_desc.c_str(),
-						  m_comTable[m_cmd_index].perm,
-						  m_sock->peer_addr(),
-						  m_user.c_str() );
+				// Authentication methods can limit the authorizations associated with
+				// a given identity (at time of coding, only TOKEN does this); apply
+				// these limits if present.
+			std::string authz_policy;
+			bool can_attempt = true;
+			if (m_policy && m_policy->EvaluateAttrString(ATTR_SEC_LIMIT_AUTHORIZATION, authz_policy)) {
+				StringList authz_limits(authz_policy.c_str());
+				authz_limits.rewind();
+				const char *perm_cstr = PermString(m_comTable[m_cmd_index].perm);
+				const char *authz_name;
+				bool found_limit = false;
+				while ( (authz_name = authz_limits.next()) ) {
+					if (!strcmp(perm_cstr, authz_name)) {
+						found_limit = true;
+						break;
+					}
+				}
+				bool has_allow_perm = !strcmp(perm_cstr, "ALLOW");
+				if (!found_limit && !has_allow_perm) {
+					can_attempt = false;
+				}
+			}
+			if (can_attempt) {
+				m_perm = daemonCore->Verify(
+					command_desc.c_str(),
+					m_comTable[m_cmd_index].perm,
+					m_sock->peer_addr(),
+					m_user.c_str() );
+			} else {
+				dprintf(D_ALWAYS, "DC_AUTHENTICATE: authentication of %s was successful but resulted in a limited authorization which did not include this command (%d %s), so aborting.\n",
+					m_sock->peer_description(),
+					m_req,
+					m_comTable[m_cmd_index].command_descrip);
+				m_perm = USER_AUTH_FAILURE;
+			}
 		}

 	} else {


Use the origin/name of the main frame in chooser dialogs

Permissions dialogs (for WebUSB, WebHID, and Web Serial) should request
permission for the main origin (the origin embedding the iframe) and not
the site hosting the iframe as per crbug.com/802945. Update the UI code
to accurately display the main origin (or extension name) and add tests.

Bug: 1280233
Change-Id: Ia3bf9274eb49c1d842e204a518635cc1187f3d3d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3353956
Reviewed-by: Scott Violet <sky@chromium.org>
Reviewed-by: Andy Paicu <andypaicu@chromium.org>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#963184}

@@ -15,11 +15,10 @@ std::u16string CreateChooserTitle(content::RenderFrameHost* render_frame_host,
                                   int title_string_id_origin) {
   if (!render_frame_host)
     return u"";
-
   return l10n_util::GetStringFUTF16(
       title_string_id_origin,
       url_formatter::FormatOriginForSecurityDisplay(
-          render_frame_host->GetLastCommittedOrigin(),
+          render_frame_host->GetMainFrame()->GetLastCommittedOrigin(),
           url_formatter::SchemeDisplay::OMIT_CRYPTOGRAPHIC));
 }


Fix Security Elevation of Privilege in Chrome installer during update

We use the `new_setup_exe` directory as the working directory for
`ArchivePatchHelper::UncompressAndPatch`. For System installs, this
directory would be under %ProgramFiles% (a directory that only admins
can write to by default) and hence a secure location.

Bug: 1279188
Change-Id: I8f65ff67d588c46d81abc09616a08e19be2820e5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3351102
Auto-Submit: S. Ganesh <ganesh@chromium.org>
Reviewed-by: Greg Thompson <grt@chromium.org>
Commit-Queue: Greg Thompson <grt@chromium.org>
Cr-Commit-Position: refs/heads/main@{#955697}

@@ -94,8 +94,10 @@ const char kMakeChromeDefault[] = "make-chrome-default";
 // Tells installer to expect to be run as a subsidiary to an MSI.
 const char kMsi[] = "msi";

-// Useful only when used with --update-setup-exe, otherwise ignored. It
-// specifies the full path where updated setup.exe will be stored.
+// Useful only when used with --update-setup-exe; otherwise ignored. Specifies
+// the full path where the updated setup.exe will be written. Any other files
+// created in the indicated directory may be deleted by the caller after process
+// termination.
 const char kNewSetupExe[] = "new-setup-exe";

 // Specifies a nonce to use with the rotate device key command.


[@container] Don't establish containers in legacy tree

Container Queries and legacy layout don't work together. Don't skip
style recalc or create a query evaluator for the cases we know we are
in a legacy tree.

Bug: 1282782
Change-Id: Ibcebf427a5eff583da1f73fb990239da99305fec
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3370281
Reviewed-by: Morten Stenshorne <mstensho@chromium.org>
Commit-Queue: Rune Lillesveen <futhark@chromium.org>
Cr-Commit-Position: refs/heads/main@{#956150}

@@ -65,7 +65,7 @@ Element* ContainerQueryEvaluator::FindContainer(
   for (Element* element = container; element;
        element = LayoutTreeBuilderTraversal::ParentElement(*element)) {
     if (const ComputedStyle* style = element->GetComputedStyle()) {
-      if (style->IsContainerForContainerQueries() &&
+      if (style->IsContainerForContainerQueries(*element) &&
           Matches(*style, container_selector)) {
         return element;
       }


[omnibox] Fix UAF bug in TemplateURLParser

This CL takes a snapshot of SearchTermsData (just like
HistoryURLProvider does) within TemplateURLParser, so we eliminate a
source of UAF bugs, where the TemplateURL parsing outlives the original
SearchTermsData.

This bug happens during Chrome shutdown.

Bug: 1278322
Change-Id: I439d9d3193bcaa7ef57ec0a046d057c32ef6fb76
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3403242
Reviewed-by: Justin Donnelly <jdonnelly@chromium.org>
Commit-Queue: Tommy Li <tommycli@chromium.org>
Cr-Commit-Position: refs/heads/main@{#961536}

@@ -275,86 +275,6 @@ GURL ConvertToHostOnly(const history::HistoryMatch& match,

 }  // namespace

-// -----------------------------------------------------------------
-// SearchTermsDataSnapshot
-
-// Implementation of SearchTermsData that takes a snapshot of another
-// SearchTermsData by copying all the responses to the different getters into
-// member strings, then returning those strings when its own getters are called.
-// This will typically be constructed on the UI thread from
-// UIThreadSearchTermsData but is subsequently safe to use on any thread.
-class SearchTermsDataSnapshot : public SearchTermsData {
- public:
-  explicit SearchTermsDataSnapshot(const SearchTermsData* search_terms_data);
-  ~SearchTermsDataSnapshot() override;
-  SearchTermsDataSnapshot(const SearchTermsDataSnapshot&) = delete;
-  SearchTermsDataSnapshot& operator=(const SearchTermsDataSnapshot&) = delete;
-
-  std::string GoogleBaseURLValue() const override;
-  std::string GetApplicationLocale() const override;
-  std::u16string GetRlzParameterValue(bool from_app_list) const override;
-  std::string GetSearchClient() const override;
-  std::string GoogleImageSearchSource() const override;
-
-  // Estimates dynamic memory usage.
-  // See base/trace_event/memory_usage_estimator.h for more info.
-  size_t EstimateMemoryUsage() const override;
-
- private:
-  std::string google_base_url_value_;
-  std::string application_locale_;
-  std::u16string rlz_parameter_value_;
-  std::string search_client_;
-  std::string google_image_search_source_;
-};
-
-SearchTermsDataSnapshot::SearchTermsDataSnapshot(
-    const SearchTermsData* search_terms_data) {
-  if (search_terms_data) {
-    google_base_url_value_ = search_terms_data->GoogleBaseURLValue();
-    application_locale_ = search_terms_data->GetApplicationLocale();
-    rlz_parameter_value_ = search_terms_data->GetRlzParameterValue(false);
-    search_client_ = search_terms_data->GetSearchClient();
-    google_image_search_source_ = search_terms_data->GoogleImageSearchSource();
-  }
-}
-
-SearchTermsDataSnapshot::~SearchTermsDataSnapshot() {
-}
-
-std::string SearchTermsDataSnapshot::GoogleBaseURLValue() const {
-  return google_base_url_value_;
-}
-
-std::string SearchTermsDataSnapshot::GetApplicationLocale() const {
-  return application_locale_;
-}
-
-std::u16string SearchTermsDataSnapshot::GetRlzParameterValue(
-    bool from_app_list) const {
-  return rlz_parameter_value_;
-}
-
-std::string SearchTermsDataSnapshot::GetSearchClient() const {
-  return search_client_;
-}
-
-std::string SearchTermsDataSnapshot::GoogleImageSearchSource() const {
-  return google_image_search_source_;
-}
-
-size_t SearchTermsDataSnapshot::EstimateMemoryUsage() const {
-  size_t res = 0;
-
-  res += base::trace_event::EstimateMemoryUsage(google_base_url_value_);
-  res += base::trace_event::EstimateMemoryUsage(application_locale_);
-  res += base::trace_event::EstimateMemoryUsage(rlz_parameter_value_);
-  res += base::trace_event::EstimateMemoryUsage(search_client_);
-  res += base::trace_event::EstimateMemoryUsage(google_image_search_source_);
-
-  return res;
-}
-
 // -----------------------------------------------------------------
 // HistoryURLProvider

@@ -463,7 +383,7 @@ HistoryURLProviderParams::HistoryURLProviderParams(
           default_search_provider
               ? new TemplateURL(default_search_provider->data())
               : nullptr),
-      search_terms_data(new SearchTermsDataSnapshot(search_terms_data)),
+      search_terms_data(SearchTermsData::MakeSnapshot(search_terms_data)),
       allow_deleting_browser_history(allow_deleting_browser_history) {}

 HistoryURLProviderParams::~HistoryURLProviderParams() {


Viz: Fix UAF on context loss

Fixed: 1250655
Change-Id: I2898316635d370fa36b94e0ae2564ed357745b2c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3413372
Auto-Submit: Ken Rockot <rockot@google.com>
Reviewed-by: Kyle Charbonneau <kylechar@chromium.org>
Commit-Queue: Kyle Charbonneau <kylechar@chromium.org>
Cr-Commit-Position: refs/heads/main@{#963012}

@@ -478,9 +478,13 @@ const gpu::GpuFeatureInfo& ContextProviderCommandBuffer::GetGpuFeatureInfo()
 void ContextProviderCommandBuffer::OnLostContext() {
   CheckValidThreadOrLockAcquired();

-  // Ensure |this| isn't destroyed in the middle of OnLostContext() if observers
-  // drop all references to it.
-  scoped_refptr<ContextProviderCommandBuffer> ref(this);
+  // Observers may drop the last persistent references to `this`, but there may
+  // be weak references in use further up the stack. This task is posted to
+  // ensure that destruction is deferred until it's safe.
+  base::SequencedTaskRunnerHandle::Get()->PostTask(
+      FROM_HERE,
+      base::BindOnce([](scoped_refptr<ContextProviderCommandBuffer>) {},
+                     base::WrapRefCounted(this)));

   for (auto& observer : observers_)
     observer.OnContextLost();


Added out-of-bounds checks for lossless symbol decoding and AC context
indices. Worked around some gcc warnings. Bumped to 1.64.

@@ -42,7 +42,7 @@
 **
 ** Represents the scan including the scan header.
 **
-** $Id: acsequentialscan.cpp,v 1.51 2016/10/28 13:58:53 thor Exp $
+** $Id: acsequentialscan.cpp,v 1.52 2022/05/23 05:56:51 thor Exp $
 **
 */

@@ -582,8 +582,7 @@ void ACSequentialScan::DecodeBlock(LONG *block,

         while(m_Coder.Get(m_Context[dc].DCMagnitude.X[i])) {
           m <<= 1;
-          i++;
-          if (m == 0)
+          if(++i >= QMContextSet::DCContextMagnitudeSet::MagnitudeContexts)
             JPG_THROW(MALFORMED_STREAM,"ACSequentialScan::DecodeBlock",
                       "QMDecoder is out of sync");
         }
@@ -653,8 +652,7 @@ void ACSequentialScan::DecodeBlock(LONG *block,

           while(m_Coder.Get(acm.X[i])) {
             m <<= 1;
-            i++;
-            if (m == 0)
+            if(++i >= QMContextSet::ACContextMagnitudeSet::MagnitudeContexts)
               JPG_THROW(MALFORMED_STREAM,"ACSequentialScan::DecodeBlock",
                         "QMDecoder is out of sync");
           }


Enhancements

Add authorization check before loading scripts.
Add detailed logs when loading preference settings.

@@ -39,6 +39,7 @@ struct extra_settings
     bool clash_classical_ruleset = false;
     std::string sort_script = "";
     std::string clash_proxies_style = "flow";
+    bool authorized = false;

     extra_settings() {};
     extra_settings(const extra_settings&) = delete;


oauth2: do not blindly accept requests with a token in the Authorization headera (781)

The logic was broken because it assumed an additional call would be
performed to the auth server, which isn't the case. Per the filter
documentation, a request is only considered subsequently authenticated
if there's valid cookie that was set after the access token was received
from the auth server:

https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/oauth2_filter

More info about how to validate an access token (which we don't do, per
above):

https://www.oauth.com/oauth2-servers/token-introspection-endpoint/
https://datatracker.ietf.org/doc/html/rfc7662

Also fix the fact that ee shouldn't be calling continueDecoding() after
decoder_callbacks_->encodeHeaders().

Signed-off-by: Raul Gutierrez Segales <rgs@pinterest.com>
Signed-off-by: Matt Klein <mklein@lyft.com>
Signed-off-by: Pradeep Rao <pcrao@google.com>

@@ -95,7 +95,7 @@ class OAuth2Test : public testing::Test {
   }

   // Set up proto fields with standard config.
-  FilterConfigSharedPtr getConfig() {
+  FilterConfigSharedPtr getConfig(bool forward_bearer_token = true) {
     envoy::extensions::filters::http::oauth2::v3::OAuth2Config p;
     auto* endpoint = p.mutable_token_endpoint();
     endpoint->set_cluster("auth.example.com");
@@ -105,7 +105,7 @@ class OAuth2Test : public testing::Test {
     p.mutable_redirect_path_matcher()->mutable_path()->set_exact(TEST_CALLBACK);
     p.set_authorization_endpoint("https://auth.example.com/oauth/authorize/");
     p.mutable_signout_path()->mutable_path()->set_exact("/_signout");
-    p.set_forward_bearer_token(true);
+    p.set_forward_bearer_token(forward_bearer_token);
     p.add_auth_scopes("user");
     p.add_auth_scopes("openid");
     p.add_auth_scopes("email");
@@ -422,6 +422,50 @@ TEST_F(OAuth2Test, OAuthOkPass) {
   EXPECT_EQ(scope_.counterFromString("test.oauth_success").value(), 1);
 }

+/**
+ * Scenario: The OAuth filter receives a request to an arbitrary path with valid OAuth cookies
+ * (cookie values and validation are mocked out), but with an invalid token in the Authorization
+ * header and forwarding bearer token is disabled.
+ *
+ * Expected behavior: the filter should sanitize the Authorization header and let the request
+ * proceed.
+ */
+TEST_F(OAuth2Test, OAuthOkPassButInvalidToken) {
+  init(getConfig(false /* forward_bearer_token */));
+
+  Http::TestRequestHeaderMapImpl mock_request_headers{
+      {Http::Headers::get().Path.get(), "/anypath"},
+      {Http::Headers::get().Host.get(), "traffic.example.com"},
+      {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get},
+      {Http::Headers::get().Scheme.get(), "https"},
+      {Http::CustomHeaders::get().Authorization.get(), "Bearer injected_malice!"},
+  };
+
+  Http::TestRequestHeaderMapImpl expected_headers{
+      {Http::Headers::get().Path.get(), "/anypath"},
+      {Http::Headers::get().Host.get(), "traffic.example.com"},
+      {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get},
+      {Http::Headers::get().Scheme.get(), "https"},
+  };
+
+  // cookie-validation mocking
+  EXPECT_CALL(*validator_, setParams(_, _));
+  EXPECT_CALL(*validator_, isValid()).WillOnce(Return(true));
+
+  // Sanitized return reference mocking
+  std::string legit_token{"legit_token"};
+  EXPECT_CALL(*validator_, token()).WillRepeatedly(ReturnRef(legit_token));
+
+  EXPECT_EQ(Http::FilterHeadersStatus::Continue,
+            filter_->decodeHeaders(mock_request_headers, false));
+
+  // Ensure that existing OAuth forwarded headers got sanitized.
+  EXPECT_EQ(mock_request_headers, expected_headers);
+
+  EXPECT_EQ(scope_.counterFromString("test.oauth_failure").value(), 0);
+  EXPECT_EQ(scope_.counterFromString("test.oauth_success").value(), 1);
+}
+
 /**
  * Scenario: The OAuth filter receives a request without valid OAuth cookies to a non-callback URL
  * (indicating that the user needs to re-validate cookies or get 401'd).
@@ -790,63 +834,41 @@ TEST_F(OAuth2Test, OAuthTestFullFlowPostWithParameters) {

   EXPECT_CALL(decoder_callbacks_,
               encodeHeaders_(HeaderMapEqualRef(&second_response_headers), true));
-  EXPECT_CALL(decoder_callbacks_, continueDecoding());

   filter_->finishFlow();
 }

 TEST_F(OAuth2Test, OAuthBearerTokenFlowFromHeader) {
-  Http::TestRequestHeaderMapImpl request_headers_before{
-      {Http::Headers::get().Path.get(), "/test?role=bearer"},
-      {Http::Headers::get().Host.get(), "traffic.example.com"},
-      {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get},
-      {Http::Headers::get().Scheme.get(), "https"},
-      {Http::CustomHeaders::get().Authorization.get(), "Bearer xyz-header-token"},
-  };
-  // Expected decoded headers after the callback & validation of the bearer token is complete.
-  Http::TestRequestHeaderMapImpl request_headers_after{
+  Http::TestRequestHeaderMapImpl request_headers{
       {Http::Headers::get().Path.get(), "/test?role=bearer"},
       {Http::Headers::get().Host.get(), "traffic.example.com"},
       {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get},
       {Http::Headers::get().Scheme.get(), "https"},
       {Http::CustomHeaders::get().Authorization.get(), "Bearer xyz-header-token"},
   };

-  // Fail the validation to trigger the OAuth flow.
+  // Fail the validation.
   EXPECT_CALL(*validator_, setParams(_, _));
   EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false));

-  EXPECT_EQ(Http::FilterHeadersStatus::Continue,
-            filter_->decodeHeaders(request_headers_before, false));
-
-  // Finally, expect that the header map had OAuth information appended to it.
-  EXPECT_EQ(request_headers_before, request_headers_after);
+  EXPECT_EQ(Http::FilterHeadersStatus::StopIteration,
+            filter_->decodeHeaders(request_headers, false));
 }

 TEST_F(OAuth2Test, OAuthBearerTokenFlowFromQueryParameters) {
-  Http::TestRequestHeaderMapImpl request_headers_before{
-      {Http::Headers::get().Path.get(), "/test?role=bearer&token=xyz-queryparam-token"},
-      {Http::Headers::get().Host.get(), "traffic.example.com"},
-      {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get},
-      {Http::Headers::get().Scheme.get(), "https"},
-  };
-  Http::TestRequestHeaderMapImpl request_headers_after{
+  Http::TestRequestHeaderMapImpl request_headers{
       {Http::Headers::get().Path.get(), "/test?role=bearer&token=xyz-queryparam-token"},
       {Http::Headers::get().Host.get(), "traffic.example.com"},
       {Http::Headers::get().Method.get(), Http::Headers::get().MethodValues.Get},
       {Http::Headers::get().Scheme.get(), "https"},
-      {Http::CustomHeaders::get().Authorization.get(), "Bearer xyz-queryparam-token"},
   };

-  // Fail the validation to trigger the OAuth flow.
+  // Fail the validation.
   EXPECT_CALL(*validator_, setParams(_, _));
   EXPECT_CALL(*validator_, isValid()).WillOnce(Return(false));

-  EXPECT_EQ(Http::FilterHeadersStatus::Continue,
-            filter_->decodeHeaders(request_headers_before, false));
-
-  // Expected decoded headers after the callback & validation of the bearer token is complete.
-  EXPECT_EQ(request_headers_before, request_headers_after);
+  EXPECT_EQ(Http::FilterHeadersStatus::StopIteration,
+            filter_->decodeHeaders(request_headers, false));
 }

 } // namespace Oauth2


@@ -21,9 +21,6 @@ namespace HttpFilters {
 namespace Oauth2 {

 namespace {
-Http::RegisterCustomInlineHeader<Http::CustomInlineHeaderRegistry::Type::RequestHeaders>
-    authorization_handle(Http::CustomHeaders::get().Authorization);
-
 constexpr const char* GetAccessTokenBodyFormatString =
     "grant_type=authorization_code&code={0}&client_id={1}&client_secret={2}&redirect_uri={3}";


decompressors: stop decompressing upon excessive compression ratio (#733)

Signed-off-by: Dmitry Rozhkov <dmitry.rozhkov@intel.com>
Co-authored-by: Ryan Hamilton <rch@google.com>
Signed-off-by: Matt Klein <mklein@lyft.com>
Signed-off-by: Pradeep Rao <pcrao@google.com>

@@ -25,6 +25,32 @@ class BrotliDecompressorImplTest : public testing::Test {
   static constexpr uint32_t default_input_size{796};
 };

+// Detect excessive compression ratio by compressing a long whitespace string
+// into a very small chunk of data and decompressing it again.
+TEST_F(BrotliDecompressorImplTest, DetectExcessiveCompressionRatio) {
+  const absl::string_view ten_whitespaces = "          ";
+  Brotli::Compressor::BrotliCompressorImpl compressor{
+      default_quality,
+      default_window_bits,
+      default_input_block_bits,
+      false,
+      Brotli::Compressor::BrotliCompressorImpl::EncoderMode::Default,
+      4096};
+  Buffer::OwnedImpl buffer;
+
+  for (int i = 0; i < 1000; i++) {
+    buffer.add(ten_whitespaces);
+  }
+
+  compressor.compress(buffer, Envoy::Compression::Compressor::State::Finish);
+
+  Buffer::OwnedImpl output_buffer;
+  Stats::IsolatedStoreImpl stats_store{};
+  BrotliDecompressorImpl decompressor{stats_store, "test.", 16, false};
+  decompressor.decompress(buffer, output_buffer);
+  EXPECT_EQ(1, stats_store.counterFromString("test.brotli_error").value());
+}
+
 // Exercises compression and decompression by compressing some data, decompressing it and then
 // comparing compressor's input/checksum with decompressor's output/checksum.
 TEST_F(BrotliDecompressorImplTest, CompressAndDecompress) {


@@ -397,6 +397,7 @@ namespace NSFontConverter
                             char nChar = *pTemp;
                             *pTemp = '\0';
                             nCode = atoi( pCur );
+							if (nCode < 0) nCode = 0;
                             *pTemp = nChar;
                             if ( nCode == 8 && *pTemp == '#')
                             {
@@ -612,7 +613,7 @@ namespace NSFontConverter
                         sToken.clear();
                         sGlyph.clear();

-                        while ( ( nChar = sEexec[++nIndex] ) != ' ' )
+                        while ( nIndex < nEexecLen && ( nChar = sEexec[++nIndex] ) != ' ' )
                             sGlyph.push_back( (wchar_t)nChar );
                     }
                 }
@@ -656,7 +657,7 @@ namespace NSFontConverter
         int nChar = 0;

         unsigned char *sBuffer = NULL;
-        int nBufLen = 0;
+        unsigned int nBufLen = 0;

         while ( nBlockType != PFB_DONE )
         {


@@ -59,9 +59,10 @@ class DLL_EXP CxIOFile : public CxFile
 		return (bool)(iErr==0);
 	}
 //////////////////////////////////////////////////////////
-	virtual size_t	Read(void *buffer, size_t size, size_t count)
+	virtual size_t	Read(void *buffer, size_t size, size_t count, void* limit_start = NULL, void* limit_end = NULL)
 	{
 		if (!m_fp) return 0;
+		clamp_buffer(buffer, size, limit_start, limit_end);
 		return fread(buffer, size, count, m_fp);
 	}
 //////////////////////////////////////////////////////////


Fix a crash bug with bogus text (Issue #426)

@@ -1,7 +1,7 @@
 /*
  * HTML exporting functions for HTMLDOC, a HTML document processing program.
  *
- * Copyright 2011-2019 by Michael R Sweet.
+ * Copyright 2011-2021 by Michael R Sweet.
  * Copyright 1997-2010 by Easy Software Products.  All rights reserved.
  *
  * This program is free software.  Distribution and use rights are outlined in
@@ -585,7 +585,7 @@ write_node(FILE   *out,		/* I - Output file */
           for (ptr = t->data; *ptr; ptr ++)
             fputs((char *)iso8859(*ptr), out);

-	  if (t->data[strlen((char *)t->data) - 1] == '\n')
+	  if (t->data[0] && t->data[strlen((char *)t->data) - 1] == '\n')
             col = 0;
 	  else
             col += strlen((char *)t->data);


CVE-2022-0497 Out-of-bounds memory access in comment parser.

Public issue:
https://github.com/openscad/openscad/issues/4043

Fix in master branch:
https://github.com/openscad/openscad/pull/4044

@@ -92,7 +92,7 @@ static std::string getComment(const std::string &fulltext, int line)
 	}

 	int end = start + 1;
-	while (fulltext[end] != '\n') end++;
+	while (end < fulltext.size() && fulltext[end] != '\n') end++;

 	std::string comment = fulltext.substr(start, end - start);