[Git][java-team/zookeeper][bookworm] 2 commits: CVE-2024-23944
Bastien Roucariès (@rouca)
gitlab at salsa.debian.org
Mon Mar 25 08:33:50 GMT 2024
Bastien Roucariès pushed to branch bookworm at Debian Java Maintainers / zookeeper
Commits:
febcd9d1 by Bastien Roucariès at 2024-03-25T08:31:05+00:00
CVE-2024-23944
- - - - -
aa97dfb2 by Bastien Roucariès at 2024-03-25T08:32:42+00:00
Add salsa CI
- - - - -
4 changed files:
- debian/changelog
- + debian/patches/0027-CVE-2024-23944-ZOOKEEPER-4799-Refactor-ACL-check-in-.patch
- debian/patches/series
- + debian/salsa-ci.yml
Changes:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,22 @@
+zookeeper (3.8.0-11+deb12u2) bookworm-security; urgency=medium
+
+ * Team upload
+ * Bug fix: CVE-2024-23944 (Closes: #1066947):
+ An information disclosure in persistent watchers handling was found in
+ Apache ZooKeeper due to missing ACL check. It allows an attacker to
+ monitor child znodes by attaching a persistent watcher (addWatch
+ command) to a parent which the attacker has already access
+ to. ZooKeeper server doesn't do ACL check when the persistent watcher
+ is triggered and as a consequence, the full path of znodes that a
+ watch event gets triggered upon is exposed to the owner of the
+ watcher. It's important to note that only the path is exposed by this
+ vulnerability, not the data of znode, but since znode path can contain
+ sensitive information like user name or login ID, this issue is
+ potentially critical.
+ * Add salsa CI
+
+ -- Bastien Roucariès <rouca at debian.org> Mon, 25 Mar 2024 08:30:56 +0000
+
zookeeper (3.8.0-11+deb12u1) bookworm-security; urgency=medium
* Team upload:
=====================================
debian/patches/0027-CVE-2024-23944-ZOOKEEPER-4799-Refactor-ACL-check-in-.patch
=====================================
@@ -0,0 +1,1223 @@
+From: Andor Molnar <andor at apache.org>
+Date: Tue, 28 Nov 2023 21:25:00 +0100
+Subject: CVE-2024-23944: ZOOKEEPER-4799: Refactor ACL check in 'addWatch'
+ command
+
+As of today, it is impossible to diagnose which watch events are dropped
+because of ACLs. Let's centralize, systematize, and log the checks at
+the 'process()' site in the Netty and NIO connections.
+
+(These 'process()' methods contain some duplicated code, and should also
+be refactored at some point. This series does not change them.)
+
+This patch also adds a substantial number of tests in order to avoid
+unexpected regressions.
+
+Co-authored-by: Patrick Hunt <phunt at apache.org>
+Co-authored-by: Damien Diederen <ddiederen at apache.org>
+
+origin: https://github.com/apache/zookeeper/commit/65b91d2d9a56157285c2a86b106e67c26520b01d
+bug: https://issues.apache.org/jira/browse/ZOOKEEPER-4799
+bug-debian-security: https://security-tracker.debian.org/tracker/CVE-2024-23944
+---
+ .../apache/zookeeper/server/watch/WatchBench.java | 6 +-
+ .../java/org/apache/zookeeper/server/DataTree.java | 23 +-
+ .../org/apache/zookeeper/server/DumbWatcher.java | 4 +-
+ .../org/apache/zookeeper/server/NIOServerCnxn.java | 16 +-
+ .../apache/zookeeper/server/NettyServerCnxn.java | 17 +-
+ .../org/apache/zookeeper/server/ServerCnxn.java | 10 +-
+ .../org/apache/zookeeper/server/ServerWatcher.java | 29 +
+ .../zookeeper/server/watch/IWatchManager.java | 7 +-
+ .../zookeeper/server/watch/WatchManager.java | 15 +-
+ .../server/watch/WatchManagerOptimized.java | 15 +-
+ .../apache/zookeeper/server/MockServerCnxn.java | 4 +-
+ .../zookeeper/server/watch/WatchManagerTest.java | 14 +-
+ .../zookeeper/test/PersistentWatcherACLTest.java | 629 +++++++++++++++++++++
+ .../zookeeper/test/UnsupportedAddWatcherTest.java | 9 +-
+ 14 files changed, 763 insertions(+), 35 deletions(-)
+ create mode 100644 zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerWatcher.java
+ create mode 100644 zookeeper-server/src/test/java/org/apache/zookeeper/test/PersistentWatcherACLTest.java
+
+diff --git a/zookeeper-it/src/main/java/org/apache/zookeeper/server/watch/WatchBench.java b/zookeeper-it/src/main/java/org/apache/zookeeper/server/watch/WatchBench.java
+index aee5b2f..afece2b 100644
+--- a/zookeeper-it/src/main/java/org/apache/zookeeper/server/watch/WatchBench.java
++++ b/zookeeper-it/src/main/java/org/apache/zookeeper/server/watch/WatchBench.java
+@@ -191,7 +191,7 @@ public class WatchBench {
+ @Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS)
+ public void testTriggerConcentrateWatch(InvocationState state) throws Exception {
+ for (String path : state.paths) {
+- state.watchManager.triggerWatch(path, event);
++ state.watchManager.triggerWatch(path, event, null);
+ }
+ }
+
+@@ -225,7 +225,7 @@ public class WatchBench {
+
+ // clear all the watches
+ for (String path : paths) {
+- watchManager.triggerWatch(path, event);
++ watchManager.triggerWatch(path, event, null);
+ }
+ }
+ }
+@@ -294,7 +294,7 @@ public class WatchBench {
+ @Measurement(iterations = 3, time = 10, timeUnit = TimeUnit.SECONDS)
+ public void testTriggerSparseWatch(TriggerSparseWatchState state) throws Exception {
+ for (String path : state.paths) {
+- state.watchManager.triggerWatch(path, event);
++ state.watchManager.triggerWatch(path, event, null);
+ }
+ }
+ }
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java
+index 2818e15..02ec6ea 100644
+--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DataTree.java
+@@ -450,7 +450,10 @@ public class DataTree {
+ if (parent == null) {
+ throw new KeeperException.NoNodeException();
+ }
++ List<ACL> parentAcl;
+ synchronized (parent) {
++ parentAcl = getACL(parent);
++
+ // Add the ACL to ACL cache first, to avoid the ACL not being
+ // created race condition during fuzzy snapshot sync.
+ //
+@@ -527,8 +530,9 @@ public class DataTree {
+ updateQuotaStat(lastPrefix, bytes, 1);
+ }
+ updateWriteStat(path, bytes);
+- dataWatches.triggerWatch(path, Event.EventType.NodeCreated);
+- childWatches.triggerWatch(parentName.equals("") ? "/" : parentName, Event.EventType.NodeChildrenChanged);
++ dataWatches.triggerWatch(path, Event.EventType.NodeCreated, acl);
++ childWatches.triggerWatch(parentName.equals("") ? "/" : parentName,
++ Event.EventType.NodeChildrenChanged, parentAcl);
+ }
+
+ /**
+@@ -568,8 +572,10 @@ public class DataTree {
+ if (node == null) {
+ throw new KeeperException.NoNodeException();
+ }
++ List<ACL> acl;
+ nodes.remove(path);
+ synchronized (node) {
++ acl = getACL(node);
+ aclCache.removeUsage(node.acl);
+ nodeDataSize.addAndGet(-getNodeSize(path, node.data));
+ }
+@@ -577,7 +583,9 @@ public class DataTree {
+ // Synchronized to sync the containers and ttls change, probably
+ // only need to sync on containers and ttls, will update it in a
+ // separate patch.
++ List<ACL> parentAcl;
+ synchronized (parent) {
++ parentAcl = getACL(parent);
+ long eowner = node.stat.getEphemeralOwner();
+ EphemeralType ephemeralType = EphemeralType.get(eowner);
+ if (ephemeralType == EphemeralType.CONTAINER) {
+@@ -624,9 +632,10 @@ public class DataTree {
+ "childWatches.triggerWatch " + parentName);
+ }
+
+- WatcherOrBitSet processed = dataWatches.triggerWatch(path, EventType.NodeDeleted);
+- childWatches.triggerWatch(path, EventType.NodeDeleted, processed);
+- childWatches.triggerWatch("".equals(parentName) ? "/" : parentName, EventType.NodeChildrenChanged);
++ WatcherOrBitSet processed = dataWatches.triggerWatch(path, EventType.NodeDeleted, acl);
++ childWatches.triggerWatch(path, EventType.NodeDeleted, acl, processed);
++ childWatches.triggerWatch("".equals(parentName) ? "/" : parentName,
++ EventType.NodeChildrenChanged, parentAcl);
+ }
+
+ public Stat setData(String path, byte[] data, int version, long zxid, long time) throws KeeperException.NoNodeException {
+@@ -635,8 +644,10 @@ public class DataTree {
+ if (n == null) {
+ throw new KeeperException.NoNodeException();
+ }
++ List<ACL> acl;
+ byte[] lastdata = null;
+ synchronized (n) {
++ acl = getACL(n);
+ lastdata = n.data;
+ nodes.preChange(path, n);
+ n.data = data;
+@@ -658,7 +669,7 @@ public class DataTree {
+ nodeDataSize.addAndGet(getNodeSize(path, data) - getNodeSize(path, lastdata));
+
+ updateWriteStat(path, dataBytes);
+- dataWatches.triggerWatch(path, EventType.NodeDataChanged);
++ dataWatches.triggerWatch(path, EventType.NodeDataChanged, acl);
+ return s;
+ }
+
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DumbWatcher.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DumbWatcher.java
+index c7bf830..f78bd8a 100644
+--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/DumbWatcher.java
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/DumbWatcher.java
+@@ -22,8 +22,10 @@ import java.io.IOException;
+ import java.net.InetSocketAddress;
+ import java.nio.ByteBuffer;
+ import java.security.cert.Certificate;
++import java.util.List;
+ import org.apache.jute.Record;
+ import org.apache.zookeeper.WatchedEvent;
++import org.apache.zookeeper.data.ACL;
+ import org.apache.zookeeper.data.Stat;
+ import org.apache.zookeeper.proto.ReplyHeader;
+
+@@ -48,7 +50,7 @@ public class DumbWatcher extends ServerCnxn {
+ }
+
+ @Override
+- public void process(WatchedEvent event) {
++ public void process(WatchedEvent event, List<ACL> znodeAcl) {
+ }
+
+ @Override
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/NIOServerCnxn.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/NIOServerCnxn.java
+index 02cde23..26eadec 100644
+--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/NIOServerCnxn.java
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/NIOServerCnxn.java
+@@ -30,14 +30,17 @@ import java.nio.channels.CancelledKeyException;
+ import java.nio.channels.SelectionKey;
+ import java.nio.channels.SocketChannel;
+ import java.security.cert.Certificate;
++import java.util.List;
+ import java.util.Queue;
+ import java.util.concurrent.LinkedBlockingQueue;
+ import java.util.concurrent.atomic.AtomicBoolean;
+ import org.apache.jute.BinaryInputArchive;
+ import org.apache.jute.Record;
+ import org.apache.zookeeper.ClientCnxn;
++import org.apache.zookeeper.KeeperException;
+ import org.apache.zookeeper.WatchedEvent;
+ import org.apache.zookeeper.ZooDefs;
++import org.apache.zookeeper.data.ACL;
+ import org.apache.zookeeper.data.Id;
+ import org.apache.zookeeper.data.Stat;
+ import org.apache.zookeeper.proto.ReplyHeader;
+@@ -689,7 +692,18 @@ public class NIOServerCnxn extends ServerCnxn {
+ * @see org.apache.zookeeper.server.ServerCnxnIface#process(org.apache.zookeeper.proto.WatcherEvent)
+ */
+ @Override
+- public void process(WatchedEvent event) {
++ public void process(WatchedEvent event, List<ACL> znodeAcl) {
++ try {
++ zkServer.checkACL(this, znodeAcl, ZooDefs.Perms.READ, getAuthInfo(), event.getPath(), null);
++ } catch (KeeperException.NoAuthException e) {
++ if (LOG.isTraceEnabled()) {
++ ZooTrace.logTraceMessage(
++ LOG,
++ ZooTrace.EVENT_DELIVERY_TRACE_MASK,
++ "Not delivering event " + event + " to 0x" + Long.toHexString(this.sessionId) + " (filtered by ACL)");
++ }
++ return;
++ }
+ ReplyHeader h = new ReplyHeader(ClientCnxn.NOTIFICATION_XID, -1L, 0);
+ if (LOG.isTraceEnabled()) {
+ ZooTrace.logTraceMessage(
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxn.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxn.java
+index 8937039..9ce11c8 100644
+--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxn.java
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxn.java
+@@ -38,11 +38,15 @@ import java.nio.ByteBuffer;
+ import java.nio.channels.SelectionKey;
+ import java.security.cert.Certificate;
+ import java.util.Arrays;
++import java.util.List;
+ import java.util.concurrent.atomic.AtomicBoolean;
+ import org.apache.jute.BinaryInputArchive;
+ import org.apache.jute.Record;
+ import org.apache.zookeeper.ClientCnxn;
++import org.apache.zookeeper.KeeperException;
+ import org.apache.zookeeper.WatchedEvent;
++import org.apache.zookeeper.ZooDefs;
++import org.apache.zookeeper.data.ACL;
+ import org.apache.zookeeper.data.Id;
+ import org.apache.zookeeper.data.Stat;
+ import org.apache.zookeeper.proto.ReplyHeader;
+@@ -159,7 +163,18 @@ public class NettyServerCnxn extends ServerCnxn {
+ }
+
+ @Override
+- public void process(WatchedEvent event) {
++ public void process(WatchedEvent event, List<ACL> znodeAcl) {
++ try {
++ zkServer.checkACL(this, znodeAcl, ZooDefs.Perms.READ, getAuthInfo(), event.getPath(), null);
++ } catch (KeeperException.NoAuthException e) {
++ if (LOG.isTraceEnabled()) {
++ ZooTrace.logTraceMessage(
++ LOG,
++ ZooTrace.EVENT_DELIVERY_TRACE_MASK,
++ "Not delivering event " + event + " to 0x" + Long.toHexString(this.sessionId) + " (filtered by ACL)");
++ }
++ return;
++ }
+ ReplyHeader h = new ReplyHeader(ClientCnxn.NOTIFICATION_XID, -1L, 0);
+ if (LOG.isTraceEnabled()) {
+ ZooTrace.logTraceMessage(
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java
+index b5b2645..7282c17 100644
+--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerCnxn.java
+@@ -39,8 +39,8 @@ import org.apache.jute.BinaryOutputArchive;
+ import org.apache.jute.Record;
+ import org.apache.zookeeper.Quotas;
+ import org.apache.zookeeper.WatchedEvent;
+-import org.apache.zookeeper.Watcher;
+ import org.apache.zookeeper.ZooDefs.OpCode;
++import org.apache.zookeeper.data.ACL;
+ import org.apache.zookeeper.data.Id;
+ import org.apache.zookeeper.data.Stat;
+ import org.apache.zookeeper.metrics.Counter;
+@@ -53,7 +53,7 @@ import org.slf4j.LoggerFactory;
+ * Interface to a Server connection - represents a connection from a client
+ * to the server.
+ */
+-public abstract class ServerCnxn implements Stats, Watcher {
++public abstract class ServerCnxn implements Stats, ServerWatcher {
+
+ // This is just an arbitrary object to represent requests issued by
+ // (aka owned by) this class
+@@ -264,7 +264,11 @@ public abstract class ServerCnxn implements Stats, Watcher {
+ /* notify the client the session is closing and close/cleanup socket */
+ public abstract void sendCloseSession();
+
+- public abstract void process(WatchedEvent event);
++ public void process(WatchedEvent event) {
++ process(event, null);
++ }
++
++ public abstract void process(WatchedEvent event, List<ACL> znodeAcl);
+
+ public abstract long getSessionId();
+
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerWatcher.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerWatcher.java
+new file mode 100644
+index 0000000..bfd4b25
+--- /dev/null
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/ServerWatcher.java
+@@ -0,0 +1,29 @@
++/*
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ *
++ * http://www.apache.org/licenses/LICENSE-2.0
++ *
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++package org.apache.zookeeper.server;
++
++import java.util.List;
++import org.apache.zookeeper.WatchedEvent;
++import org.apache.zookeeper.Watcher;
++import org.apache.zookeeper.data.ACL;
++
++public interface ServerWatcher extends Watcher {
++
++ void process(WatchedEvent event, List<ACL> znodeAcl);
++
++}
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/IWatchManager.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/IWatchManager.java
+index 1bc44c8..b612dd7 100644
+--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/IWatchManager.java
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/IWatchManager.java
+@@ -19,8 +19,10 @@
+ package org.apache.zookeeper.server.watch;
+
+ import java.io.PrintWriter;
++import java.util.List;
+ import org.apache.zookeeper.Watcher;
+ import org.apache.zookeeper.Watcher.Event.EventType;
++import org.apache.zookeeper.data.ACL;
+
+ public interface IWatchManager {
+
+@@ -82,10 +84,11 @@ public interface IWatchManager {
+ *
+ * @param path znode path
+ * @param type the watch event type
++ * @param acl ACL of the znode in path
+ *
+ * @return the watchers have been notified
+ */
+- WatcherOrBitSet triggerWatch(String path, EventType type);
++ WatcherOrBitSet triggerWatch(String path, EventType type, List<ACL> acl);
+
+ /**
+ * Distribute the watch event for the given path, but ignore those
+@@ -97,7 +100,7 @@ public interface IWatchManager {
+ *
+ * @return the watchers have been notified
+ */
+- WatcherOrBitSet triggerWatch(String path, EventType type, WatcherOrBitSet suppress);
++ WatcherOrBitSet triggerWatch(String path, EventType type, List<ACL> acl, WatcherOrBitSet suppress);
+
+ /**
+ * Get the size of watchers.
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManager.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManager.java
+index c5b1330..0c24c73 100644
+--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManager.java
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManager.java
+@@ -22,6 +22,7 @@ import java.io.PrintWriter;
+ import java.util.HashMap;
+ import java.util.HashSet;
+ import java.util.Iterator;
++import java.util.List;
+ import java.util.Map;
+ import java.util.Map.Entry;
+ import java.util.Set;
+@@ -29,8 +30,10 @@ import org.apache.zookeeper.WatchedEvent;
+ import org.apache.zookeeper.Watcher;
+ import org.apache.zookeeper.Watcher.Event.EventType;
+ import org.apache.zookeeper.Watcher.Event.KeeperState;
++import org.apache.zookeeper.data.ACL;
+ import org.apache.zookeeper.server.ServerCnxn;
+ import org.apache.zookeeper.server.ServerMetrics;
++import org.apache.zookeeper.server.ServerWatcher;
+ import org.apache.zookeeper.server.ZooTrace;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+@@ -115,12 +118,12 @@ public class WatchManager implements IWatchManager {
+ }
+
+ @Override
+- public WatcherOrBitSet triggerWatch(String path, EventType type) {
+- return triggerWatch(path, type, null);
++ public WatcherOrBitSet triggerWatch(String path, EventType type, List<ACL> acl) {
++ return triggerWatch(path, type, acl, null);
+ }
+
+ @Override
+- public WatcherOrBitSet triggerWatch(String path, EventType type, WatcherOrBitSet supress) {
++ public WatcherOrBitSet triggerWatch(String path, EventType type, List<ACL> acl, WatcherOrBitSet supress) {
+ WatchedEvent e = new WatchedEvent(type, KeeperState.SyncConnected, path);
+ Set<Watcher> watchers = new HashSet<>();
+ PathParentIterator pathParentIterator = getPathParentIterator(path);
+@@ -165,7 +168,11 @@ public class WatchManager implements IWatchManager {
+ if (supress != null && supress.contains(w)) {
+ continue;
+ }
+- w.process(e);
++ if (w instanceof ServerWatcher) {
++ ((ServerWatcher) w).process(e, acl);
++ } else {
++ w.process(e);
++ }
+ }
+
+ switch (type) {
+diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManagerOptimized.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManagerOptimized.java
+index 1cc7deb..947a5b6 100644
+--- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManagerOptimized.java
++++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/watch/WatchManagerOptimized.java
+@@ -22,6 +22,7 @@ import java.io.PrintWriter;
+ import java.util.BitSet;
+ import java.util.HashMap;
+ import java.util.HashSet;
++import java.util.List;
+ import java.util.Map;
+ import java.util.Map.Entry;
+ import java.util.Set;
+@@ -31,8 +32,10 @@ import org.apache.zookeeper.WatchedEvent;
+ import org.apache.zookeeper.Watcher;
+ import org.apache.zookeeper.Watcher.Event.EventType;
+ import org.apache.zookeeper.Watcher.Event.KeeperState;
++import org.apache.zookeeper.data.ACL;
+ import org.apache.zookeeper.server.ServerCnxn;
+ import org.apache.zookeeper.server.ServerMetrics;
++import org.apache.zookeeper.server.ServerWatcher;
+ import org.apache.zookeeper.server.util.BitHashSet;
+ import org.apache.zookeeper.server.util.BitMap;
+ import org.slf4j.Logger;
+@@ -202,12 +205,12 @@ public class WatchManagerOptimized implements IWatchManager, IDeadWatcherListene
+ }
+
+ @Override
+- public WatcherOrBitSet triggerWatch(String path, EventType type) {
+- return triggerWatch(path, type, null);
++ public WatcherOrBitSet triggerWatch(String path, EventType type, List<ACL> acl) {
++ return triggerWatch(path, type, acl, null);
+ }
+
+ @Override
+- public WatcherOrBitSet triggerWatch(String path, EventType type, WatcherOrBitSet suppress) {
++ public WatcherOrBitSet triggerWatch(String path, EventType type, List<ACL> acl, WatcherOrBitSet suppress) {
+ WatchedEvent e = new WatchedEvent(type, KeeperState.SyncConnected, path);
+
+ BitHashSet watchers = remove(path);
+@@ -232,7 +235,11 @@ public class WatchManagerOptimized implements IWatchManager, IDeadWatcherListene
+ continue;
+ }
+
+- w.process(e);
++ if (w instanceof ServerWatcher) {
++ ((ServerWatcher) w).process(e, acl);
++ } else {
++ w.process(e);
++ }
+ triggeredWatches++;
+ }
+ }
+diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/server/MockServerCnxn.java b/zookeeper-server/src/test/java/org/apache/zookeeper/server/MockServerCnxn.java
+index 4dfcebd..af09592 100644
+--- a/zookeeper-server/src/test/java/org/apache/zookeeper/server/MockServerCnxn.java
++++ b/zookeeper-server/src/test/java/org/apache/zookeeper/server/MockServerCnxn.java
+@@ -22,8 +22,10 @@ import java.io.IOException;
+ import java.net.InetSocketAddress;
+ import java.nio.ByteBuffer;
+ import java.security.cert.Certificate;
++import java.util.List;
+ import org.apache.jute.Record;
+ import org.apache.zookeeper.WatchedEvent;
++import org.apache.zookeeper.data.ACL;
+ import org.apache.zookeeper.data.Stat;
+ import org.apache.zookeeper.proto.ReplyHeader;
+
+@@ -56,7 +58,7 @@ public class MockServerCnxn extends ServerCnxn {
+ }
+
+ @Override
+- public void process(WatchedEvent event) {
++ public void process(WatchedEvent event, List<ACL> acl) {
+ }
+
+ @Override
+diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/server/watch/WatchManagerTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/server/watch/WatchManagerTest.java
+index dc90e07..c71cac5 100644
+--- a/zookeeper-server/src/test/java/org/apache/zookeeper/server/watch/WatchManagerTest.java
++++ b/zookeeper-server/src/test/java/org/apache/zookeeper/server/watch/WatchManagerTest.java
+@@ -130,7 +130,7 @@ public class WatchManagerTest extends ZKTestCase {
+ public void run() {
+ while (!stopped) {
+ String path = PATH_PREFIX + r.nextInt(paths);
+- WatcherOrBitSet s = manager.triggerWatch(path, EventType.NodeDeleted);
++ WatcherOrBitSet s = manager.triggerWatch(path, EventType.NodeDeleted, null);
+ if (s != null) {
+ triggeredCount.addAndGet(s.size());
+ }
+@@ -433,20 +433,20 @@ public class WatchManagerTest extends ZKTestCase {
+ //path2 is watched by watcher1
+ manager.addWatch(path2, watcher1);
+
+- manager.triggerWatch(path3, EventType.NodeCreated);
++ manager.triggerWatch(path3, EventType.NodeCreated, null);
+ //path3 is not being watched so metric is 0
+ checkMetrics("node_created_watch_count", 0L, 0L, 0D, 0L, 0L);
+
+ //path1 is watched by two watchers so two fired
+- manager.triggerWatch(path1, EventType.NodeCreated);
++ manager.triggerWatch(path1, EventType.NodeCreated, null);
+ checkMetrics("node_created_watch_count", 2L, 2L, 2D, 1L, 2L);
+
+ //path2 is watched by one watcher so one fired now total is 3
+- manager.triggerWatch(path2, EventType.NodeCreated);
++ manager.triggerWatch(path2, EventType.NodeCreated, null);
+ checkMetrics("node_created_watch_count", 1L, 2L, 1.5D, 2L, 3L);
+
+ //watches on path1 are no longer there so zero fired
+- manager.triggerWatch(path1, EventType.NodeDataChanged);
++ manager.triggerWatch(path1, EventType.NodeDataChanged, null);
+ checkMetrics("node_changed_watch_count", 0L, 0L, 0D, 0L, 0L);
+
+ //both wather1 and wather2 are watching path1
+@@ -456,10 +456,10 @@ public class WatchManagerTest extends ZKTestCase {
+ //path2 is watched by watcher1
+ manager.addWatch(path2, watcher1);
+
+- manager.triggerWatch(path1, EventType.NodeDataChanged);
++ manager.triggerWatch(path1, EventType.NodeDataChanged, null);
+ checkMetrics("node_changed_watch_count", 2L, 2L, 2D, 1L, 2L);
+
+- manager.triggerWatch(path2, EventType.NodeDeleted);
++ manager.triggerWatch(path2, EventType.NodeDeleted, null);
+ checkMetrics("node_deleted_watch_count", 1L, 1L, 1D, 1L, 1L);
+
+ //make sure that node created watch count is not impacted by the fire of other event types
+diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/test/PersistentWatcherACLTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/test/PersistentWatcherACLTest.java
+new file mode 100644
+index 0000000..1597a48
+--- /dev/null
++++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/PersistentWatcherACLTest.java
+@@ -0,0 +1,629 @@
++/**
++ * Licensed to the Apache Software Foundation (ASF) under one
++ * or more contributor license agreements. See the NOTICE file
++ * distributed with this work for additional information
++ * regarding copyright ownership. The ASF licenses this file
++ * to you under the Apache License, Version 2.0 (the
++ * "License"); you may not use this file except in compliance
++ * with the License. You may obtain a copy of the License at
++ * <p>
++ * http://www.apache.org/licenses/LICENSE-2.0
++ * <p>
++ * Unless required by applicable law or agreed to in writing, software
++ * distributed under the License is distributed on an "AS IS" BASIS,
++ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
++ * See the License for the specific language governing permissions and
++ * limitations under the License.
++ */
++
++package org.apache.zookeeper.test;
++
++import static org.apache.zookeeper.AddWatchMode.PERSISTENT;
++import static org.apache.zookeeper.AddWatchMode.PERSISTENT_RECURSIVE;
++import static org.junit.jupiter.api.Assertions.assertEquals;
++import static org.junit.jupiter.api.Assertions.assertNotNull;
++import static org.junit.jupiter.api.Assertions.assertNull;
++import static org.junit.jupiter.api.Assertions.fail;
++import java.io.IOException;
++import java.util.Collections;
++import java.util.List;
++import java.util.concurrent.BlockingQueue;
++import java.util.concurrent.LinkedBlockingQueue;
++import java.util.concurrent.TimeUnit;
++import org.apache.zookeeper.AddWatchMode;
++import org.apache.zookeeper.CreateMode;
++import org.apache.zookeeper.KeeperException;
++import org.apache.zookeeper.WatchedEvent;
++import org.apache.zookeeper.Watcher;
++import org.apache.zookeeper.Watcher.Event.EventType;
++import org.apache.zookeeper.ZooDefs;
++import org.apache.zookeeper.ZooKeeper;
++import org.apache.zookeeper.data.ACL;
++import org.junit.jupiter.api.BeforeEach;
++import org.junit.jupiter.api.Test;
++import org.slf4j.Logger;
++import org.slf4j.LoggerFactory;
++
++/**
++ * This class encodes a set of tests corresponding to a "truth table"
++ * of interactions between persistent watchers and znode ACLs:
++ *
++ * <a href="https://docs.google.com/spreadsheets/d/1eMH2aimrrMc_b6McU8CHm2yCj2X-w30Fy4fCBOHn7NA/edit#gid=0">https://docs.google.com/spreadsheets/d/1eMH2aimrrMc_b6McU8CHm2yCj2X-w30Fy4fCBOHn7NA/edit#gid=0</a>
++ */
++public class PersistentWatcherACLTest extends ClientBase {
++ private static final Logger LOG = LoggerFactory.getLogger(PersistentWatcherACLTest.class);
++ /** An ACL denying READ. */
++ private static final List<ACL> ACL_NO_READ = Collections.singletonList(new ACL(ZooDefs.Perms.ALL & ~ZooDefs.Perms.READ, ZooDefs.Ids.ANYONE_ID_UNSAFE));
++ private BlockingQueue<WatchedEvent> events;
++ private Watcher persistentWatcher;
++
++ @Override
++ @BeforeEach
++ public void setUp() throws Exception {
++ super.setUp();
++
++ events = new LinkedBlockingQueue<>();
++ persistentWatcher = event -> {
++ events.add(event);
++ LOG.info("Added event: {}; total: {}", event, events.size());
++ };
++ }
++
++ /**
++ * This Step class, with the Round class below, is used to encode
++ * the contents of the truth table.
++ *
++ * (These should become Records once we target JDK 14+.)
++ */
++ private static class Step {
++ Step(int opCode, String target) {
++ this(opCode, target, null, null);
++ }
++ Step(int opCode, String target, EventType eventType, String eventPath) {
++ this.opCode = opCode;
++ this.target = target;
++ this.eventType = eventType;
++ this.eventPath = eventPath;
++ }
++ /** Action: create, setData or delete */
++ final int opCode;
++ /** Target path */
++ final String target;
++ /** Expected event type, {@code null} if no event is expected */
++ final EventType eventType;
++ /** Expected event path, {@code null} if no event is expected */
++ final String eventPath;
++ }
++
++ /**
++ * This Round class, with the Step class above, is used to encode
++ * the contents of the truth table.
++ *
++ * (These should become Records once we target JDK 14+.)
++ */
++ private static class Round {
++ Round(String summary, Boolean allowA, Boolean allowB, Boolean allowC, String watchTarget, AddWatchMode watchMode, Step[] steps) {
++ this.summary = summary;
++ this.allowA = allowA;
++ this.allowB = allowB;
++ this.allowC = allowC;
++ this.watchTarget = watchTarget;
++ this.watchMode = watchMode;
++ this.steps = steps;
++ }
++ /** Notes/summary */
++ final String summary;
++ /** Should /a's ACL leave it readable? */
++ final Boolean allowA;
++ /** Should /a/b's ACL leave it readable? */
++ final Boolean allowB;
++ /** Should /a/b/c's ACL leave it readable? */
++ final Boolean allowC;
++ /** Watch path */
++ final String watchTarget;
++ /** Watch mode */
++ final AddWatchMode watchMode;
++ /** Actions and expected events */
++ final Step[] steps;
++ }
++
++ /**
++ * A "round" of tests from the table encoded as Java objects.
++ *
++ * Note that the set of rounds is collected in a {@code ROUNDS}
++ * array below, and that this test class includes a {@code main}
++ * method which produces a "CSV" rendition of the table, for ease
++ * of comparison with the original.
++ *
++ * @see #ROUNDS
++ */
++ private static final Round roundNothingAsAIsWatchedButDeniedBIsNotWatched =
++ new Round(
++ "Nothing as a is watched but denied. b is not watched",
++ false, true, null, "/a", PERSISTENT, new Step[] {
++ new Step(ZooDefs.OpCode.setData, "/a"),
++ new Step(ZooDefs.OpCode.create, "/a/b"),
++ new Step(ZooDefs.OpCode.setData, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundNothingAsBothAAndBDenied =
++ new Round(
++ "Nothing as both a and b denied",
++ false, false, null, "/a", PERSISTENT, new Step[] {
++ new Step(ZooDefs.OpCode.setData, "/a"),
++ new Step(ZooDefs.OpCode.create, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundAChangesInclChildrenAreSeen =
++ new Round(
++ "a changes, incl children, are seen",
++ true, false, null, "/a", PERSISTENT, new Step[] {
++ new Step(ZooDefs.OpCode.create, "/a", EventType.NodeCreated, "/a"),
++ new Step(ZooDefs.OpCode.setData, "/a", EventType.NodeDataChanged, "/a"),
++ new Step(ZooDefs.OpCode.create, "/a/b", EventType.NodeChildrenChanged, "/a"),
++ new Step(ZooDefs.OpCode.setData, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a/b", EventType.NodeChildrenChanged, "/a"),
++ new Step(ZooDefs.OpCode.delete, "/a", EventType.NodeDeleted, "/a"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundNothingForAAsItSDeniedBChangesSeen =
++ new Round(
++ "Nothing for a as it's denied, b changes allowed/seen",
++ false, true, null, "/a", PERSISTENT_RECURSIVE, new Step[] {
++ new Step(ZooDefs.OpCode.setData, "/a"),
++ new Step(ZooDefs.OpCode.create, "/a/b", EventType.NodeCreated, "/a/b"),
++ new Step(ZooDefs.OpCode.setData, "/a/b", EventType.NodeDataChanged, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a/b", EventType.NodeDeleted, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundNothingBothDenied =
++ new Round(
++ "Nothing - both denied",
++ false, false, null, "/a", PERSISTENT_RECURSIVE, new Step[] {
++ new Step(ZooDefs.OpCode.setData, "/a"),
++ new Step(ZooDefs.OpCode.create, "/a/b"),
++ new Step(ZooDefs.OpCode.setData, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a/b"),
++ new Step(ZooDefs.OpCode.delete, "/a"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundNothingAllDenied =
++ new Round(
++ "Nothing - all denied",
++ false, false, false, "/a", PERSISTENT_RECURSIVE, new Step[] {
++ new Step(ZooDefs.OpCode.create, "/a/b"),
++ new Step(ZooDefs.OpCode.setData, "/a/b"),
++ new Step(ZooDefs.OpCode.create, "/a/b/c"),
++ new Step(ZooDefs.OpCode.setData, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundADeniesSeeAllChangesForBAndCIncludingBChildren =
++ new Round(
++ "a denies, see all changes for b and c, including b's children",
++ false, true, true, "/a", PERSISTENT_RECURSIVE, new Step[] {
++ new Step(ZooDefs.OpCode.create, "/a/b", EventType.NodeCreated, "/a/b"),
++ new Step(ZooDefs.OpCode.setData, "/a/b", EventType.NodeDataChanged, "/a/b"),
++ new Step(ZooDefs.OpCode.create, "/a/b/c", EventType.NodeCreated, "/a/b/c"),
++ new Step(ZooDefs.OpCode.setData, "/a/b/c", EventType.NodeDataChanged, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b/c", EventType.NodeDeleted, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b", EventType.NodeDeleted, "/a/b"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundADeniesSeeAllBChangesAndBChildrenNothingForC =
++ new Round(
++ "a denies, see all b changes and b's children, nothing for c",
++ false, true, false, "/a", PERSISTENT_RECURSIVE, new Step[] {
++ new Step(ZooDefs.OpCode.create, "/a/b", EventType.NodeCreated, "/a/b"),
++ new Step(ZooDefs.OpCode.setData, "/a/b", EventType.NodeDataChanged, "/a/b"),
++ new Step(ZooDefs.OpCode.create, "/a/b/c"),
++ new Step(ZooDefs.OpCode.setData, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b", EventType.NodeDeleted, "/a/b"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundNothingTheWatchIsOnC =
++ new Round(
++ "Nothing - the watch is on c",
++ false, true, false, "/a/b/c", PERSISTENT_RECURSIVE, new Step[] {
++ new Step(ZooDefs.OpCode.create, "/a/b"),
++ new Step(ZooDefs.OpCode.setData, "/a/b"),
++ new Step(ZooDefs.OpCode.create, "/a/b/c"),
++ new Step(ZooDefs.OpCode.setData, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b"),
++ }
++ );
++
++ /**
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round roundTheWatchIsOnlyOnCBAndCAllowed =
++ new Round(
++ "The watch is only on c (b and c allowed)",
++ false, true, true, "/a/b/c", PERSISTENT_RECURSIVE, new Step[] {
++ new Step(ZooDefs.OpCode.create, "/a/b"),
++ new Step(ZooDefs.OpCode.setData, "/a/b"),
++ new Step(ZooDefs.OpCode.create, "/a/b/c", EventType.NodeCreated, "/a/b/c"),
++ new Step(ZooDefs.OpCode.setData, "/a/b/c", EventType.NodeDataChanged, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b/c", EventType.NodeDeleted, "/a/b/c"),
++ new Step(ZooDefs.OpCode.delete, "/a/b"),
++ }
++ );
++
++ /**
++ * Transform the "tristate" {@code allow} property to a concrete
++ * ACL which can be passed to the ZooKeeper API.
++ *
++ * @param allow "tristate" value: {@code null}/don't care, {@code
++ * true}, {@code false}
++ * @return the ACL
++ */
++ private static List<ACL> selectAcl(Boolean allow) {
++ if (allow == null) {
++ return null;
++ } else if (!allow) {
++ return ACL_NO_READ;
++ } else {
++ return ZooDefs.Ids.OPEN_ACL_UNSAFE;
++ }
++ }
++
++ /**
++ * Executes one "round" of tests from the Java object encoding of
++ * the table.
++ *
++ * @param round the "round"
++ *
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see PersistentWatcherACLTest.Round
++ * @see PersistentWatcherACLTest.Step
++ */
++ private void execRound(Round round)
++ throws IOException, InterruptedException, KeeperException {
++ try (ZooKeeper zk = createClient(new CountdownWatcher(), hostPort)) {
++ List<ACL> aclForA = selectAcl(round.allowA);
++ List<ACL> aclForB = selectAcl(round.allowB);
++ List<ACL> aclForC = selectAcl(round.allowC);
++
++ boolean firstStepCreatesA = round.steps.length > 0
++ && round.steps[0].opCode == ZooDefs.OpCode.create
++ && round.steps[0].target.equals("/a");
++
++ // Assume /a always exists (except if it's about to be created)
++ if (!firstStepCreatesA) {
++ zk.create("/a", new byte[0], aclForA, CreateMode.PERSISTENT);
++ }
++
++ zk.addWatch(round.watchTarget, persistentWatcher, round.watchMode);
++
++ for (int i = 0; i < round.steps.length; i++) {
++ Step step = round.steps[i];
++
++ switch (step.opCode) {
++ case ZooDefs.OpCode.create:
++ List<ACL> acl = step.target.endsWith("/c")
++ ? aclForC
++ : step.target.endsWith("/b")
++ ? aclForB
++ : aclForA;
++ zk.create(step.target, new byte[0], acl, CreateMode.PERSISTENT);
++ break;
++ case ZooDefs.OpCode.delete:
++ zk.delete(step.target, -1);
++ break;
++ case ZooDefs.OpCode.setData:
++ zk.setData(step.target, new byte[0], -1);
++ break;
++ default:
++ fail("Unexpected opCode " + step.opCode + " in step " + i);
++ break;
++ }
++
++ WatchedEvent actualEvent = events.poll(500, TimeUnit.MILLISECONDS);
++ if (step.eventType == null) {
++ assertNull(actualEvent, "Unexpected event " + actualEvent + " at step " + i);
++ } else {
++ String m = "In event " + actualEvent + " at step " + i;
++ assertNotNull(actualEvent, m);
++ assertEquals(step.eventType, actualEvent.getType(), m);
++ assertEquals(step.eventPath, actualEvent.getPath(), m);
++ }
++ }
++ }
++ }
++
++ /**
++ * A test method, wrapping the definition of a "round." This
++ * should really use JUnit 5's runtime test case generation
++ * facilities, but that would prevent backporting this suite to
++ * JUnit 4.
++ *
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see <a href="https://junit.org/junit5/docs/5.0.2/api/org/junit/jupiter/api/DynamicTest.html">JUnit 5 runtime test case generation</a>
++ */
++ @Test
++ public void testNothingAsAIsWatchedButDeniedBIsNotWatched()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundNothingAsAIsWatchedButDeniedBIsNotWatched);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundNothingAsBothAAndBDenied
++ */
++ @Test
++ public void testNothingAsBothAAndBDenied()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundNothingAsBothAAndBDenied);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundAChangesInclChildrenAreSeen
++ */
++ @Test
++ public void testAChangesInclChildrenAreSeen()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundAChangesInclChildrenAreSeen);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundNothingForAAsItSDeniedBChangesSeen
++ */
++ @Test
++ public void testNothingForAAsItSDeniedBChangesSeen()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundNothingForAAsItSDeniedBChangesSeen);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundNothingBothDenied
++ */
++ @Test
++ public void testNothingBothDenied()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundNothingBothDenied);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundNothingAllDenied
++ */
++ @Test
++ public void testNothingAllDenied()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundNothingAllDenied);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundADeniesSeeAllChangesForBAndCIncludingBChildren
++ */
++ @Test
++ public void testADeniesSeeAllChangesForBAndCIncludingBChildren()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundADeniesSeeAllChangesForBAndCIncludingBChildren);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundADeniesSeeAllBChangesAndBChildrenNothingForC
++ */
++ @Test
++ public void testADeniesSeeAllBChangesAndBChildrenNothingForC()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundADeniesSeeAllBChangesAndBChildrenNothingForC);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundNothingTheWatchIsOnC
++ */
++ @Test
++ public void testNothingTheWatchIsOnC()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundNothingTheWatchIsOnC);
++ }
++
++ /**
++ * @see #testNothingAsAIsWatchedButDeniedBIsNotWatched
++ * @see #roundTheWatchIsOnlyOnCBAndCAllowed
++ */
++ @Test
++ public void testTheWatchIsOnlyOnCBAndCAllowed()
++ throws IOException, InterruptedException, KeeperException {
++ execRound(roundTheWatchIsOnlyOnCBAndCAllowed);
++ }
++
++ // The rest of this class is the world's lamest "CSV" encoder.
++
++ /**
++ * The set of rounds. This array includes one entry for each
++ * {@code private static final Round round*} member variable
++ * defined above.
++ *
++ * @see #roundNothingAsAIsWatchedButDeniedBIsNotWatched
++ */
++ private static final Round[] ROUNDS = new Round[] {
++ roundNothingAsAIsWatchedButDeniedBIsNotWatched,
++ roundNothingAsBothAAndBDenied,
++ roundAChangesInclChildrenAreSeen,
++ roundNothingForAAsItSDeniedBChangesSeen,
++ roundNothingBothDenied,
++ roundNothingAllDenied,
++ roundADeniesSeeAllChangesForBAndCIncludingBChildren,
++ roundADeniesSeeAllBChangesAndBChildrenNothingForC,
++ roundNothingTheWatchIsOnC,
++ roundTheWatchIsOnlyOnCBAndCAllowed,
++ };
++
++ private static String allowString(String prefix, Boolean allow) {
++ if (allow == null) {
++ return "";
++ } else {
++ return prefix + (allow ? "allow" : "deny");
++ }
++ }
++
++ private static String watchModeString(AddWatchMode watchMode) {
++ switch (watchMode) {
++ case PERSISTENT:
++ return "PERSISTENT";
++ case PERSISTENT_RECURSIVE:
++ return "PRECURSIVE";
++ default:
++ return "?";
++ }
++ }
++
++ private static String actionString(int opCode) {
++ switch (opCode) {
++ case ZooDefs.OpCode.create:
++ return "create";
++ case ZooDefs.OpCode.delete:
++ return "delete";
++ case ZooDefs.OpCode.setData:
++ return "modify";
++ default:
++ return "?";
++ }
++ }
++
++ private static String eventPathString(String eventPath) {
++ if (eventPath == null) {
++ return "?";
++ } else if (eventPath.length() <= 1) {
++ return eventPath;
++ } else {
++ return eventPath.substring(eventPath.lastIndexOf('/') + 1);
++ }
++ }
++
++ /**
++ * Generates a "CSV" rendition of the table in sb.
++ *
++ * @param sb the target string builder
++ */
++ private static void genCsv(StringBuilder sb) {
++ sb.append("Initial State,")
++ .append("Action,")
++ .append("NodeCreated,")
++ .append("NodeDeleted,")
++ .append("NodeDataChanged,")
++ .append("NodeChildrenChanged,")
++ .append("Notes/summary\n");
++ sb.append("Assume /a always exists\n\n");
++
++ for (Round round : ROUNDS) {
++ sb.append("\"ACL")
++ .append(allowString(": a ", round.allowA))
++ .append(allowString(", b ", round.allowB))
++ .append(allowString(", c ", round.allowC))
++ .append("\"")
++ .append(",,,,,,\"")
++ .append(round.summary)
++ .append("\"\n");
++ for (int i = 0; i < round.steps.length; i++) {
++ Step step = round.steps[i];
++
++ if (i == 0) {
++ sb.append("\"addWatch(")
++ .append(round.watchTarget)
++ .append(", ")
++ .append(watchModeString(round.watchMode))
++ .append(")\"");
++ }
++
++ sb.append(",")
++ .append(actionString(step.opCode))
++ .append(" ")
++ .append(step.target)
++ .append(",");
++
++ if (step.eventType == EventType.NodeCreated) {
++ sb.append("y - ")
++ .append(eventPathString(step.eventPath));
++ }
++
++ sb.append(",");
++
++ if (step.eventType == EventType.NodeDeleted) {
++ sb.append("y - ")
++ .append(eventPathString(step.eventPath));
++ }
++
++ sb.append(",");
++
++ if (step.eventType == EventType.NodeDataChanged) {
++ sb.append("y - ")
++ .append(eventPathString(step.eventPath));
++ }
++
++ sb.append(",");
++
++ if (round.watchMode == PERSISTENT_RECURSIVE) {
++ sb.append("n");
++ } else if (step.eventType == EventType.NodeChildrenChanged) {
++ sb.append("y - ")
++ .append(eventPathString(step.eventPath));
++ }
++
++ sb.append("\n");
++ }
++
++ sb.append("\n");
++ }
++ }
++
++ /**
++ * Generates a "CSV" rendition of the table to standard output.
++ *
++ * @see #ROUNDS
++ */
++ public static void main(String[] args) {
++ StringBuilder sb = new StringBuilder();
++ genCsv(sb);
++ System.out.println(sb);
++ }
++}
+diff --git a/zookeeper-server/src/test/java/org/apache/zookeeper/test/UnsupportedAddWatcherTest.java b/zookeeper-server/src/test/java/org/apache/zookeeper/test/UnsupportedAddWatcherTest.java
+index a3d6eef..4a46a9c 100644
+--- a/zookeeper-server/src/test/java/org/apache/zookeeper/test/UnsupportedAddWatcherTest.java
++++ b/zookeeper-server/src/test/java/org/apache/zookeeper/test/UnsupportedAddWatcherTest.java
+@@ -21,10 +21,14 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
+ import java.io.IOException;
+ import java.io.PrintWriter;
+ import java.util.Collections;
++import java.util.List;
+ import org.apache.zookeeper.AddWatchMode;
++import org.apache.zookeeper.CreateMode;
+ import org.apache.zookeeper.KeeperException;
+ import org.apache.zookeeper.Watcher;
++import org.apache.zookeeper.ZooDefs;
+ import org.apache.zookeeper.ZooKeeper;
++import org.apache.zookeeper.data.ACL;
+ import org.apache.zookeeper.server.watch.IWatchManager;
+ import org.apache.zookeeper.server.watch.WatchManagerFactory;
+ import org.apache.zookeeper.server.watch.WatcherOrBitSet;
+@@ -59,12 +63,12 @@ public class UnsupportedAddWatcherTest extends ClientBase {
+ }
+
+ @Override
+- public WatcherOrBitSet triggerWatch(String path, Watcher.Event.EventType type) {
++ public WatcherOrBitSet triggerWatch(String path, Watcher.Event.EventType type, List<ACL> acl) {
+ return new WatcherOrBitSet(Collections.emptySet());
+ }
+
+ @Override
+- public WatcherOrBitSet triggerWatch(String path, Watcher.Event.EventType type, WatcherOrBitSet suppress) {
++ public WatcherOrBitSet triggerWatch(String path, Watcher.Event.EventType type, List<ACL> acl, WatcherOrBitSet suppress) {
+ return new WatcherOrBitSet(Collections.emptySet());
+ }
+
+@@ -120,6 +124,7 @@ public class UnsupportedAddWatcherTest extends ClientBase {
+ try (ZooKeeper zk = createClient(hostPort)) {
+ // the server will generate an exception as our custom watch manager doesn't implement
+ // the new version of addWatch()
++ zk.create("/foo", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+ zk.addWatch("/foo", event -> {
+ }, AddWatchMode.PERSISTENT_RECURSIVE);
+ }
=====================================
debian/patches/series
=====================================
@@ -1,19 +1,10 @@
-#01-add-jtoaster-to-zooinspector.patch
-#02-patch-build-system.patch
03-disable-cygwin-detection.patch
05-ZOOKEEPER-770.patch
06-ftbfs-gcc-4.7.patch
07-remove-non-reproducible-manifest-entries.patch
-#08-reproducible-javadoc.patch
10-cppunit-pkg-config.patch
11-disable-minikdc-tests.patch
12-add-yetus-annotations.patch
-#13-disable-netty-connection-factory.patch
-#14-ftbfs-with-gcc-8.patch
-#15-javadoc-doclet.patch
-#16-ZOOKEEPER-1392.patch
-#17-gcc9-ftbfs-925869.patch
-#18-java17-compatibility.patch
19-add_missing-plugins-versions.patch
20-no-Timeout-in-tests.patch
21-use-ValueSource-with-ints.patch
@@ -33,3 +24,4 @@
35-flaky-test.patch
36-JUnitPlatform-deprecation.patch
CVE-2023-44981.patch
+0027-CVE-2024-23944-ZOOKEEPER-4799-Refactor-ACL-check-in-.patch
=====================================
debian/salsa-ci.yml
=====================================
@@ -0,0 +1,7 @@
+---
+include:
+ - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/recipes/debian.yml
+
+variables:
+ RELEASE: 'bookworm'
+
View it on GitLab: https://salsa.debian.org/java-team/zookeeper/-/compare/68e7b28e8451eef95f75f3426bc2d4f2eed51de9...aa97dfb23add76d3bd9bbbceaf4dea982aa7f922
--
View it on GitLab: https://salsa.debian.org/java-team/zookeeper/-/compare/68e7b28e8451eef95f75f3426bc2d4f2eed51de9...aa97dfb23add76d3bd9bbbceaf4dea982aa7f922
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20240325/f8bd380f/attachment.htm>
More information about the pkg-java-commits
mailing list