Initial change set is preserved for as long as the monitor itself.
However, if a new client has a condition on a column that is not
one of the monitored columns, this column will be added to the
monitor via ovsdb_monitor_condition_bind().  This new column, however,
doesn't exist in the initial change set.  That will cause ovsdb-server
to malfunction or crash trying to access non-existent column during
condition evaluation:

 ERROR: AddressSanitizer: heap-buffer-overflow
 READ of size 4 at 0x606000006780 thread T0
     0 ovsdb_clause_evaluate ovsdb/condition.c:328:26
     1 ovsdb_condition_match_any_clause ovsdb/condition.c:441:13
     2 ovsdb_condition_empty_or_match_any ovsdb/condition.h:84:13
     3 ovsdb_monitor_row_update_type_condition ovsdb/monitor.c:892:28
     4 ovsdb_monitor_compose_row_update2 ovsdb/monitor.c:1058:12
     5 ovsdb_monitor_compose_update ovsdb/monitor.c:1172:24
     6 ovsdb_monitor_get_update ovsdb/monitor.c:1276:24
     7 ovsdb_jsonrpc_monitor_create ovsdb/jsonrpc-server.c:1505:12
     8 ovsdb_jsonrpc_session_got_request ovsdb/jsonrpc-server.c:1030:21
     9 ovsdb_jsonrpc_session_run ovsdb/jsonrpc-server.c:572:17
    10 ovsdb_jsonrpc_session_run_all ovsdb/jsonrpc-server.c:602:21
    11 ovsdb_jsonrpc_server_run ovsdb/jsonrpc-server.c:417:9
    12 main_loop ovsdb/ovsdb-server.c:222:9
    13 main ovsdb/ovsdb-server.c:500:5
    14 __libc_start_call_main
    15 __libc_start_main@GLIBC_2.2.5
    16 _start (ovsdb/ovsdb-server+0x473034)

 Located 0 bytes after 64-byte region [0x606000006740,0x606000006780)
 allocated by thread T0 here:
     0 malloc (ovsdb/ovsdb-server+0x50dc82)
     1 xmalloc__ lib/util.c:140:15
     2 xmalloc lib/util.c:175:12
     3 clone_monitor_row_data ovsdb/monitor.c:336:12
     4 ovsdb_monitor_changes_update ovsdb/monitor.c:1384:23
     5 ovsdb_monitor_get_initial ovsdb/monitor.c:1535:21
     6 ovsdb_jsonrpc_monitor_create ovsdb/jsonrpc-server.c:1502:9
     7 ovsdb_jsonrpc_session_got_request ovsdb/jsonrpc-server.c:1030:21
     8 ovsdb_jsonrpc_session_run ovsdb/jsonrpc-server.c:572:17
     9 ovsdb_jsonrpc_session_run_all ovsdb/jsonrpc-server.c:602:21
    10 ovsdb_jsonrpc_server_run ovsdb/jsonrpc-server.c:417:9
    11 main_loop ovsdb/ovsdb-server.c:222:9
    12 main ovsdb/ovsdb-server.c:500:5
    13 __libc_start_call_main
    14 __libc_start_main@GLIBC_2.2.5
    15 _start (ovsdb/ovsdb-server+0x473034)

Fix that by destroying the initial change set every time new columns
are added to the monitor.  This will trigger re-generation of the
change set and it will contain all the necessary columns afterwards.

Fixes: 07c27226ee96 ("ovsdb: Monitor: Keep and maintain the initial change 
set.")
Reported-by: Han Zhou <[email protected]>
Signed-off-by: Ilya Maximets <[email protected]>
---
 ovsdb/monitor.c        | 15 +++++++++-
 tests/ovsdb-monitor.at | 66 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 80 insertions(+), 1 deletion(-)

diff --git a/ovsdb/monitor.c b/ovsdb/monitor.c
index 04dcd2298..4afaa89f4 100644
--- a/ovsdb/monitor.c
+++ b/ovsdb/monitor.c
@@ -478,6 +478,7 @@ ovsdb_monitor_add_column(struct ovsdb_monitor *dbmon,
                          enum ovsdb_monitor_selection select,
                          bool monitored)
 {
+    struct ovsdb_monitor_change_set *mcs;
     struct ovsdb_monitor_table *mt;
     struct ovsdb_monitor_column *c;
 
@@ -488,6 +489,18 @@ ovsdb_monitor_add_column(struct ovsdb_monitor *dbmon,
         return column->name;
     }
 
+    mcs = dbmon->init_change_set;
+    if (mcs) {
+        /* A new column is going to be added to the monitor.  Existing
+         * initial change set doesn't have it, so can no longer be used.
+         * Initial change set is never used by more than one session at
+         * the same time, so it's safe to destroy it here. */
+        ovs_assert(mcs->n_refs == 1);
+        ovsdb_monitor_json_cache_destroy(dbmon, mcs);
+        ovsdb_monitor_change_set_destroy(mcs);
+        dbmon->init_change_set = NULL;
+    }
+
     if (mt->n_columns >= mt->allocated_columns) {
         mt->columns = x2nrealloc(mt->columns, &mt->allocated_columns,
                                  sizeof *mt->columns);
@@ -614,7 +627,7 @@ ovsdb_monitor_untrack_change_set(struct ovsdb_monitor 
*dbmon,
     if (--mcs->n_refs == 0) {
         if (mcs == dbmon->init_change_set) {
             /* The initial change set should exist as long as the
-             * monitor itself. */
+             * monitor doesn't change. */
             mcs->n_refs++;
             return;
         } else if (mcs == dbmon->new_change_set) {
diff --git a/tests/ovsdb-monitor.at b/tests/ovsdb-monitor.at
index 7e1ff64f0..12cd2bc31 100644
--- a/tests/ovsdb-monitor.at
+++ b/tests/ovsdb-monitor.at
@@ -1011,3 +1011,69 @@ row,action,name,number,_version
 ]], [ignore])
 AT_CLEANUP
 
+AT_SETUP([monitor-cond initial reply with condition on non-monitored column])
+AT_KEYWORDS([ovsdb server monitor monitor-cond positive initial non-monitored])
+
+ordinal_schema > schema
+AT_CHECK([ovsdb-tool create db schema], [0], [stdout], [ignore])
+on_exit 'kill `cat ovsdb-server.pid`'
+AT_CAPTURE_FILE([ovsdb-server.log])
+AT_CHECK([ovsdb-server --detach --no-chdir --pidfile \
+            --remote=punix:socket --log-file db], [0], [ignore], [ignore])
+
+dnl Initialize the database content.
+for txn in m4_foreach([txn], [[[["ordinals",
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 0, "name": "zero"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 1, "name": "one"}},
+      {"op": "insert",
+       "table": "ordinals",
+       "row": {"number": 2, "name": "two"}}]]]], ['txn' ]); do
+  AT_CHECK([ovsdb-client transact unix:socket "$txn"], [0], [ignore], [ignore])
+done
+
+dnl Start a first client that monitors only the column 'name'.
+on_exit 'kill `cat client-1.pid`'
+AT_CAPTURE_FILE([client-1.out])
+AT_CHECK([ovsdb-client -vjsonrpc --pidfile=client-1.pid --detach --no-chdir \
+            -d json monitor-cond --format=csv unix:socket \
+            ordinals '[[true]]' ordinals ["name"] \
+            > client-1.out 2> client-1.err], [0], [ignore], [ignore])
+dnl Wait for the initial monitor reply.
+OVS_WAIT_UNTIL([grep -q 'initial' client-1.out])
+
+dnl Start a second client that monitors the column 'name', but has a condition
+dnl on column 'number'.
+on_exit 'kill `cat client-2.pid`'
+AT_CAPTURE_FILE([client-2.out])
+AT_CHECK([ovsdb-client -vjsonrpc --pidfile=client-2.pid --detach --no-chdir \
+            -d json monitor-cond --format=csv unix:socket \
+            ordinals '[[["number", "!=", 1]]]' ordinals ["name"] \
+            > client-2.out 2> client-2.err], [0], [ignore], [ignore])
+dnl Wait for the initial monitor reply.
+OVS_WAIT_UNTIL([grep -q 'initial' client-2.out])
+
+OVSDB_SERVER_SHUTDOWN
+OVS_WAIT_UNTIL([test ! -e ovsdb-server.pid && \
+                    test ! -e client-1.pid && test ! -e client-2.pid])
+
+dnl The first client should have all the names.
+AT_CHECK([$PYTHON3 $srcdir/ovsdb-monitor-sort.py < client-1.out | uuidfilt],
+         [0], [dnl
+row,action,name
+<0>,initial,"""one"""
+<1>,initial,"""two"""
+<2>,initial,"""zero"""
+])
+
+dnl The second client should not have the name 'one'.
+AT_CHECK([$PYTHON3 $srcdir/ovsdb-monitor-sort.py < client-2.out | uuidfilt],
+         [0], [dnl
+row,action,name
+<0>,initial,"""two"""
+<1>,initial,"""zero"""
+])
+AT_CLEANUP
-- 
2.40.1

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to