You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@flink.apache.org by GitBox <gi...@apache.org> on 2020/12/27 14:51:10 UTC

[GitHub] [flink] hwanju opened a new pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

hwanju opened a new pull request #14499:
URL: https://github.com/apache/flink/pull/14499


   <!--
   *Thank you very much for contributing to Apache Flink - we are happy that you want to help us improve Flink. To help the community review your contribution in the best possible way, please go through the checklist below, which will get the contribution into a shape in which it can be best reviewed.*
   
   *Please understand that we do not do this to make contributions to Flink a hassle. In order to uphold a high standard of quality for code contributions, while at the same time managing a large number of contributions, we need contributors to prepare the contributions well, and give reviewers enough contextual information for the review. Please also understand that contributions that do not follow this guide will take longer to review and thus typically be picked up with lower priority by the community.*
   
   ## Contribution Checklist
   
     - Make sure that the pull request corresponds to a [JIRA issue](https://issues.apache.org/jira/projects/FLINK/issues). Exceptions are made for typos in JavaDoc or documentation files, which need no JIRA issue.
     
     - Name the pull request in the form "[FLINK-XXXX] [component] Title of the pull request", where *FLINK-XXXX* should be replaced by the actual issue number. Skip *component* if you are unsure about which is the best component.
     Typo fixes that have no associated JIRA issue should be named following this pattern: `[hotfix] [docs] Fix typo in event time introduction` or `[hotfix] [javadocs] Expand JavaDoc for PuncuatedWatermarkGenerator`.
   
     - Fill out the template below to describe the changes contributed by the pull request. That will give reviewers the context they need to do the review.
     
     - Make sure that the change passes the automated tests, i.e., `mvn clean verify` passes. You can set up Azure Pipelines CI to do that following [this guide](https://cwiki.apache.org/confluence/display/FLINK/Azure+Pipelines#AzurePipelines-Tutorial:SettingupAzurePipelinesforaforkoftheFlinkrepository).
   
     - Each pull request should address only one issue, not mix up code from multiple issues.
     
     - Each commit in the pull request has a meaningful commit message (including the JIRA id)
   
     - Once all items of the checklist are addressed, remove the above text and this checklist, leaving only the filled out template below.
   
   
   **(The sections below can be removed for hotfixes of typos)**
   -->
   
   ## What is the purpose of the change
   
   Flink app or dependent libraries can terminate JVM by System.exit or halt, by which JM or TM is terminated. As this has happened typically by accident (e.g., following normal Java program practice), such termination attempt can better be detected and warned with logging or disallowed with throwing an exception. This change adds a configuration to detect and take an action (warn or throw). The option is disabled by default, so normal users have no behavioral change. In the case of throw option, termination attempt is converted to an exception, so it can be bubbled up through stack to user. Note that this patch does not cover all the user code execution parts. It's currently best effort. Where to cover is to be discussed via review and follow-up.
   
   ## Brief change log
   
     - Added `security.check-system-exit` option to check and take configured action on JVM termination.
     - Added `FlinkUserSecurityManager` as a general security manager for user code, where currently only exit check is performed if configured.
     - Added monitor/unmonitor section for code parts where user code can run (main, StreamTask.invoke, cancel, checkpoint).
   
   ## Verifying this change
   
   This change added tests and can be verified as follows:
   
     - Added unit test, `FlinkUserSecurityManagerTest`, for general verification of `FlinkUserSecurityManager`.
     - Added unit test, `StreamTaskSystemExitTest`, for validation of use of `FlinkUserSecurityManager` in StreamTask.
     - Manually tested with an app that has System.exit() in main and user-defined function. 
     - Verified doc is generated by `docs/build_docs.sh -p`
     - Azure pipeline build - https://dev.azure.com/hwandori/Flink-public/_build/results?buildId=19&view=results
    
   ## Does this pull request potentially affect one of the following parts:
   
     - Dependencies (does it add or upgrade a dependency): no
     - The public API, i.e., is any changed class annotated with `@Public(Evolving)`: no
     - The serializers: no
     - The runtime per-record code paths (performance sensitive): no
     - Anything that affects deployment or recovery: JobManager (and its components), Checkpointing, Kubernetes/Yarn/Mesos, ZooKeeper: no
     - The S3 file system connector: no
   
   ## Documentation
   
     - Does this pull request introduce a new feature? no
     - If yes, how is the feature documented? not applicable 
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555767838



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {

Review comment:
       You can see some difference. `test*Configuration` is a little more end-to-end via configuration setting (testing `fromConfiguration`), while others are not using `Configuration` rather directly using constructor.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     }, {
       "hash" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157",
       "triggerID" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "triggerType" : "PUSH"
     }, {
       "hash" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12186",
       "triggerID" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 7c7133b540da13a268fddabd97cc873509210f7a Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157) 
   * 359060d80001a5406849d8c7f4b8c58344238ed1 Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12186) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559619820



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,425 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitHandlerTriggered() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, exitStatus::set);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitHandlerTriggeredWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, true);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        try {
+            newSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail("Expect exception to be thrown");
+        } catch (UserSystemExitException ue) {
+        }
+        assertThat(newSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testMultiSecurityManagersWithSetLastAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        TestExitSecurityManager oldSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(oldSecurityManager);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        try {
+            System.getSecurityManager().checkExit(TEST_EXIT_CODE);
+        } catch (UserSystemExitException ue) {

Review comment:
       ```suggestion
           try {
               System.getSecurityManager().checkExit(TEST_EXIT_CODE);
               fail("Expect exception to be thrown");
           } catch (UserSystemExitException ue) {
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r549124277



##########
File path: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
##########
@@ -869,6 +883,8 @@ private boolean triggerCheckpoint(
 					"invokable was not in state running.", checkpointMetaData.getCheckpointId(), getName(), e);
 				return false;
 			}
+		} finally {
+			FlinkUserSecurityManager.unmonitorSystemExitForCurrentThread();
 		}

Review comment:
       This is to protect against user-defined checkpoint function, but couldn't find a fast way to cover this in unit test (other than manual test). As user-defined checkpoint is unlikely to have exit function, I am fine to remove this instrumentation, so please let me know.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751476681


   Thanks a lot for your contribution to the Apache Flink project. I'm the @flinkbot. I help the community
   to review your pull request. We will use this comment to track the progress of the review.
   
   
   ## Automated Checks
   Last check on commit 84a20ae008df61a397e91e2238bb52afcf3e5124 (Sun Dec 27 14:54:00 UTC 2020)
   
   **Warnings:**
    * No documentation files were touched! Remember to keep the Flink docs up to date!
   
   
   <sub>Mention the bot in a comment to re-run the automated checks.</sub>
   ## Review Progress
   
   * ❓ 1. The [description] looks good.
   * ❓ 2. There is [consensus] that the contribution should go into to Flink.
   * ❓ 3. Needs [attention] from.
   * ❓ 4. The change fits into the overall [architecture].
   * ❓ 5. Overall code [quality] is good.
   
   Please see the [Pull Request Review Guide](https://flink.apache.org/contributing/reviewing-prs.html) for a full explanation of the review process.<details>
    The Bot is tracking the review progress through labels. Labels are applied according to the order of the review items. For consensus, approval by a Flink committer of PMC member is required <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot approve description` to approve one or more aspects (aspects: `description`, `consensus`, `architecture` and `quality`)
    - `@flinkbot approve all` to approve all aspects
    - `@flinkbot approve-until architecture` to approve everything until `architecture`
    - `@flinkbot attention @username1 [@username2 ..]` to require somebody's attention
    - `@flinkbot disapprove architecture` to remove an approval you gave earlier
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r557728452



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -115,4 +127,35 @@ public static boolean isDeclarativeResourceManagementEnabled(Configuration confi
         return configuration.get(ENABLE_DECLARATIVE_RESOURCE_MANAGEMENT)
                 && !System.getProperties().containsKey("flink.tests.disable-declarative");
     }
+
+    /** The mode of how to handle user code attempting to exit JVM. */
+    public enum UserSystemExitMode {
+        DISABLED("No check is enabled, that is allowing exit without any action"),

Review comment:
       Last write win! Followed `Flink is not monitoring or intercepting calls to System.exit()`. @XComp, please let me know if you want rewording.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555548218



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "
+                                        + "security manager features (e.g., '%s: %s', '%s: %s')",
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.key(),
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.defaultValue(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.key(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.defaultValue(),
+                                e));
+            }
+        }
+        FlinkSecurityManager.flinkSecurityManager = flinkSecurityManager;
+    }
+
+    public static void monitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.monitorUserSystemExit();
+        }
+    }
+
+    public static void unmonitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.unmonitorUserSystemExit();
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm);
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm, context);
+        }
+    }
+
+    @Override
+    public void checkExit(int status) {
+        if (userSystemExitMonitored()) {
+            switch (userSystemExitMode) {
+                case DISABLED:
+                    break;
+                case LOG:
+                    // Add exception trace log to help users to debug where exit came from.
+                    LOG.warn(
+                            "Exiting JVM with status {} is monitored, logging and exiting",
+                            status,
+                            new UserSystemExitException());
+                    break;
+                case THROW:
+                    throw new UserSystemExitException();
+                default:
+                    // Must not happen if exhaustively handling all modes above. Logging as being
+                    // already at exit path.
+                    LOG.warn("No valid check exit mode configured: {}", userSystemExitMode);
+            }
+        }
+        // As this security manager is current at outer most of the chain and it has exit guard
+        // option, invoke inner security manager here after passing guard checking above, if any.
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkExit(status);
+        }
+        // At this point, exit is determined. Invoke exit handler if defined, otherwise check ended.
+        if (onExitBehavior != null) {
+            // Unset ourselves to allow exiting.
+            System.setSecurityManager(null);
+            // Execute the desired behavior, e.g. forceful exit instead of proceeding with
+            // System.exit().
+            onExitBehavior.accept(status);

Review comment:
       Why can't we simplify this to
   ```java
   if (haltOnSystemExit) {
     System.setSecurityManager(null);
     Runtime.getRuntime().halt(status);
   }
   ```
   




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559581770



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,418 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitHandlerTriggered() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, exitStatus::set);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitHandlerTriggeredWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, true);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(newSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetLastAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        TestExitSecurityManager oldSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(oldSecurityManager);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        System.getSecurityManager().checkExit(TEST_EXIT_CODE);

Review comment:
       ```suggestion
           try {
               System.getSecurityManager().checkExit(TEST_EXIT_CODE);
           } catch (UserSystemExitException ue) {
           }
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "FAILURE",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 3ea63637fe7a8c46e434fe3661fe9df444e389a3 Azure: [FAILURE](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405) 
   * 03e163b7c14a96058f14dd003cbffe8844a78f85 UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-761572560


   > I believe sources are relatively important, in particular because some users have their own implementations there.
   > @hwanju do you want to address this in this PR, or in a follow up?
   
   This is nice catch! This should be addressed as part of this PR. When this patch was written, the old Flink version did invoke `run()` in `invoke()` in the same thread, but now with mailbox approach, it spawns a separate thread for run(). Yes, `FlinkSecurityManager` supports thread-inherited monitoring flag, so why this spawned thread wasn't protected? The reason is the thread local variables are inherited at the time of thread construction, not thread run, and the construction happens in `loadAndInstantiateInvokable` before `invoke`. By wrapping `loadAndInstantiateInvokable` with exit monitoring, I see it's protected as follows:
   
   ```
   org.apache.flink.runtime.UserSystemExitException: Flink user code attempted to exit JVM.
   	at org.apache.flink.runtime.security.FlinkSecurityManager.checkExit(FlinkSecurityManager.java:184)
   	at java.lang.Runtime.exit(Runtime.java:107)
   	at java.lang.System.exit(System.java:971)
   	at org.apache.flink.streaming.tests.FailureTestSource.run(FailureTestSource.java:65)
   	at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:110)
   	at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:66)
   	at org.apache.flink.streaming.runtime.tasks.SourceStreamTask$LegacySourceFunctionThread.run(SourceStreamTask.java:245)
   ```
   
   Added unit test (reproduced without fix, and fixed after it).


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r557494228



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "
+                                        + "security manager features (e.g., '%s: %s', '%s: %s')",
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.key(),
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.defaultValue(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.key(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.defaultValue(),
+                                e));
+            }
+        }
+        FlinkSecurityManager.flinkSecurityManager = flinkSecurityManager;
+    }
+
+    public static void monitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.monitorUserSystemExit();
+        }
+    }
+
+    public static void unmonitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.unmonitorUserSystemExit();
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm);
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm, context);
+        }
+    }
+
+    @Override
+    public void checkExit(int status) {
+        if (userSystemExitMonitored()) {
+            switch (userSystemExitMode) {
+                case DISABLED:
+                    break;
+                case LOG:
+                    // Add exception trace log to help users to debug where exit came from.
+                    LOG.warn(
+                            "Exiting JVM with status {} is monitored, logging and exiting",
+                            status,
+                            new UserSystemExitException());
+                    break;
+                case THROW:
+                    throw new UserSystemExitException();
+                default:
+                    // Must not happen if exhaustively handling all modes above. Logging as being
+                    // already at exit path.
+                    LOG.warn("No valid check exit mode configured: {}", userSystemExitMode);
+            }
+        }
+        // As this security manager is current at outer most of the chain and it has exit guard
+        // option, invoke inner security manager here after passing guard checking above, if any.
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkExit(status);
+        }
+        // At this point, exit is determined. Invoke exit handler if defined, otherwise check ended.
+        if (onExitBehavior != null) {
+            // Unset ourselves to allow exiting.
+            System.setSecurityManager(null);
+            // Execute the desired behavior, e.g. forceful exit instead of proceeding with
+            // System.exit().
+            onExitBehavior.accept(status);

Review comment:
       Okay, understood




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "FAILURE",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 03e163b7c14a96058f14dd003cbffe8844a78f85 Azure: [FAILURE](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512) 
   * b3f5f5084ab6af669a4f79c47977ad16bab50650 UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 84a20ae008df61a397e91e2238bb52afcf3e5124 Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559623752



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,425 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitHandlerTriggered() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, exitStatus::set);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitHandlerTriggeredWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, true);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        try {
+            newSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail("Expect exception to be thrown");
+        } catch (UserSystemExitException ue) {
+        }
+        assertThat(newSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testMultiSecurityManagersWithSetLastAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        TestExitSecurityManager oldSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(oldSecurityManager);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        try {
+            System.getSecurityManager().checkExit(TEST_EXIT_CODE);
+        } catch (UserSystemExitException ue) {

Review comment:
       ah damn. thanks.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r557506153



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -88,21 +93,28 @@
                                     .build());
 
     @Documentation.Section(Documentation.Sections.EXPERT_CLUSTER)
-    public static final ConfigOption<Boolean> HALT_ON_FATAL_ERROR =
-            key("cluster.processes.halt-on-fatal-error")
+    public static final ConfigOption<Boolean> HALT_ON_SYSTEM_EXIT =
+            key("cluster.processes.halt-on-system-exit")

Review comment:
       No problem at all. Naming is hard and it's very simple change :)




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r550363293



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest {
+
+	private static final int TEST_EXIT_CODE = 123;
+	private FlinkUserSecurityManager flinkUserSecurityManager;
+
+	@Before
+	public void setUp() {
+		flinkUserSecurityManager = new FlinkUserSecurityManager(FlinkUserSecurityManager.CheckExitMode.THROW);
+		System.setSecurityManager(flinkUserSecurityManager);
+	}
+
+	@After
+	public void tearDown() {
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager.getOriginalSecurityManager());
+		}
+	}
+
+	@Test(expected = UserSystemExitException.class)
+	public void testThrowUserExit() {
+		flinkUserSecurityManager.monitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testToggleUserExit() {
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		flinkUserSecurityManager.unmonitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testPerThreadThrowUserExit() throws Exception {
+		ExecutorService executorService = Executors.newSingleThreadExecutor();
+		// Async thread test before enabling monitoring ensures it does not throw while prestarting
+		// worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+		CompletableFuture<Void> future =
+			CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		// This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+		future = CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+	}
+
+	@Test
+	public void testInheritedThrowUserExit() throws Exception {
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		Thread thread = new Thread(() -> {
+			try {
+				flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+				fail();
+			} catch (UserSystemExitException ignored) {
+			} catch (Throwable t) {
+				fail();
+			}
+		});
+		thread.start();
+		thread.join();
+	}
+
+	@Test
+	public void testWarnUserExit() {
+		// Warn mode enables monitor but only logging allowing exit, hence not expecting exception.
+		// NOTE - Do not specifically test warning logging.
+		System.setSecurityManager(flinkUserSecurityManager.getOriginalSecurityManager());
+		flinkUserSecurityManager = new FlinkUserSecurityManager(FlinkUserSecurityManager.CheckExitMode.WARN);
+		System.setSecurityManager(flinkUserSecurityManager);
+		flinkUserSecurityManager.monitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testValidConfiguration() {

Review comment:
       Will do.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555769208



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "
+                                        + "security manager features (e.g., '%s: %s', '%s: %s')",
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.key(),
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.defaultValue(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.key(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.defaultValue(),
+                                e));
+            }
+        }
+        FlinkSecurityManager.flinkSecurityManager = flinkSecurityManager;
+    }
+
+    public static void monitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.monitorUserSystemExit();
+        }
+    }
+
+    public static void unmonitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.unmonitorUserSystemExit();
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm);
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm, context);
+        }
+    }
+
+    @Override
+    public void checkExit(int status) {
+        if (userSystemExitMonitored()) {
+            switch (userSystemExitMode) {
+                case DISABLED:
+                    break;
+                case LOG:
+                    // Add exception trace log to help users to debug where exit came from.
+                    LOG.warn(
+                            "Exiting JVM with status {} is monitored, logging and exiting",
+                            status,
+                            new UserSystemExitException());

Review comment:
       A true, cool - that makes sense. Thanks for clarification. :-)




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     }, {
       "hash" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157",
       "triggerID" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "triggerType" : "PUSH"
     }, {
       "hash" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 7c7133b540da13a268fddabd97cc873509210f7a Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157) 
   * 359060d80001a5406849d8c7f4b8c58344238ed1 UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r558339332



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -115,4 +127,35 @@ public static boolean isDeclarativeResourceManagementEnabled(Configuration confi
         return configuration.get(ENABLE_DECLARATIVE_RESOURCE_MANAGEMENT)
                 && !System.getProperties().containsKey("flink.tests.disable-declarative");
     }
+
+    /** The mode of how to handle user code attempting to exit JVM. */
+    public enum UserSystemExitMode {
+        DISABLED("Flink is not monitoring or intercepting calls to System.exit()"),
+        LOG("Log exit attempt with stack trace but still allowing exit to be performed"),
+        THROW("Throw exception when exit is attempted disallowing JVM termination");
+
+        private final String description;
+
+        UserSystemExitMode(String description) {
+            this.description = description;
+        }
+
+        public static Description getConfigDescription() {
+            Description.DescriptionBuilder builder = Description.builder();
+            List<TextElement> modeDescriptions =
+                    new ArrayList<>(UserSystemExitMode.values().length);
+            builder.text(
+                    "Flag to check user code exiting system by terminating JVM (e.g., System.exit())");
+            for (UserSystemExitMode mode : UserSystemExitMode.values()) {
+                modeDescriptions.add(
+                        text(String.format("%s - %s", mode.name(), mode.getDescription())));
+            }
+            builder.list(modeDescriptions.toArray(new TextElement[modeDescriptions.size()]));

Review comment:
       Two questions:
   1. Above header explanation, `Flag to check user code exiting system by terminating JVM` is added already to indicate it's for user code. Do you think one more emphasis at the end like you described?
   2. In your suggested documentation, I think `HALT_ON_FATAL_ERROR` should be `INTERCEPT_USER_SYSTEM_EXIT`.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r558329410



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {

Review comment:
       This is only true for code-paths executing user code. 
   I'm adding a suggestion to the documentation.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-752619323


   I'm going to add the remarks on where System.exit handling should be added here separately:
   
   Two other locations @rmetzger mentioned in [FLINK-15156](https://issues.apache.org/jira/browse/FLINK-15156) were:
   * [ExecutionGraph.vertexFinished:1149](https://github.com/apache/flink/blob/c6997c97c575d334679915c328792b8a3067cfb5/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java#L1149) where it calls `finalizeOnMaster(..)` on each `JobVertex`
   * [ExecutionGraphBuilder.buildGraph:222](https://github.com/apache/flink/blob/c6997c97c575d334679915c328792b8a3067cfb5/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java#L222) calls `initializeOnMaster(..)` on each `JobVertex`
   I might not have the full understanding of the call hierarchies: Are these two locations covered by your current version of the code?
   
   @rmetzger May you have another look?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555381736



##########
File path: flink-clients/src/main/java/org/apache/flink/client/program/PackagedProgram.java
##########
@@ -210,7 +211,12 @@ public String getDescription() throws ProgramInvocationException {
      * local execution by default.
      */
     public void invokeInteractiveModeForExecution() throws ProgramInvocationException {
-        callMainMethod(mainClass, args);
+        FlinkUserSecurityManager.monitorSystemExitForCurrentThread();

Review comment:
       IIUC, it's okay not to have `FlinkUserSecurityManager` in `CliFrontend`, as our focus is more on JM like crashing Flink cluster itself. Do you want me to also apply this monitoring to CliFrontend? Currently, I have not done it.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     }, {
       "hash" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157",
       "triggerID" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "triggerType" : "PUSH"
     }, {
       "hash" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12186",
       "triggerID" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "triggerType" : "PUSH"
     }, {
       "hash" : "6bd776d5be680b296e2ab125482fa6bbb63f3c6b",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "6bd776d5be680b296e2ab125482fa6bbb63f3c6b",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 7c7133b540da13a268fddabd97cc873509210f7a Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157) 
   * 359060d80001a5406849d8c7f4b8c58344238ed1 Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12186) 
   * 6bd776d5be680b296e2ab125482fa6bbb63f3c6b UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 84a20ae008df61a397e91e2238bb52afcf3e5124 UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555765193



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -88,21 +93,28 @@
                                     .build());
 
     @Documentation.Section(Documentation.Sections.EXPERT_CLUSTER)
-    public static final ConfigOption<Boolean> HALT_ON_FATAL_ERROR =
-            key("cluster.processes.halt-on-fatal-error")
+    public static final ConfigOption<Boolean> HALT_ON_SYSTEM_EXIT =
+            key("cluster.processes.halt-on-system-exit")

Review comment:
       >Renaming an existing parameter has a bigger impact on users as they possibly have to change their existing configuration files when upgrading.
   
   This was Robert's suggestion and I also had the same concern. But I incorporated that suggestion because this flag seems to be more of advanced expert feature, which may be used by a few individuals, and I see sometimes Flink config names have been changed slightly or largely. However, as we do not know if this feature is used much, I am fine to keep the name same. I am willing to follow votes.
   
   > would be ok with that change if we decide to merge both parameters into one as they are more or less just defining some special exit behavior. What do you think?
   
   Like your following comment, user system exit vs. global system exit behaviors need to be separate for clarity. Those can be under the same `ClusterOptions` but I am still leaning toward having separate configurations.
   
   Having said that, the only alternative we can think is just to keep `cluster.process.halt-on-fatal-error` as is. What do you think @rmetzger ?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r551822019



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest extend TestLogger {
+
+	private static final int TEST_EXIT_CODE = 123;
+	private FlinkUserSecurityManager flinkUserSecurityManager;
+
+	@Before
+	public void setUp() {
+		flinkUserSecurityManager = new FlinkUserSecurityManager(FlinkUserSecurityManager.CheckExitMode.THROW);
+		System.setSecurityManager(flinkUserSecurityManager);
+	}
+
+	@After
+	public void tearDown() {
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager.getOriginalSecurityManager());
+		}
+	}
+
+	@Test(expected = UserSystemExitException.class)
+	public void testThrowUserExit() {
+		flinkUserSecurityManager.monitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testToggleUserExit() {
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		flinkUserSecurityManager.unmonitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testPerThreadThrowUserExit() throws Exception {
+		ExecutorService executorService = Executors.newSingleThreadExecutor();
+		// Async thread test before enabling monitoring ensures it does not throw while prestarting
+		// worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+		CompletableFuture<Void> future =
+			CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		// This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+		future = CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+	}
+
+	@Test
+	public void testInheritedThrowUserExit() throws Exception {
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		Thread thread = new Thread(() -> {
+			try {
+				flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+				fail();
+			} catch (UserSystemExitException ignored) {
+			} catch (Throwable t) {
+				fail();

Review comment:
       This `fail()` won't fail the test, as it is thrown from the thread. JUnit only fails a test if the main thread fails.
   
   Maybe there's a nicer solution, but that's what I came up with:
   ```java
       @Test
       public void testInheritedThrowUserExit() throws Throwable {
           flinkUserSecurityManager.monitorSystemExit();
           try {
               flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
               fail();
           } catch (UserSystemExitException ignored) {
           }
           Tuple1<Throwable> threadError = Tuple1.of(null);
           Thread thread =
                   new Thread(
                           () -> {
                               try {
                                   flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
                                   threadError.f0 = new AssertionError();
                               } catch (UserSystemExitException ignored) {
                               } catch (Throwable t) {
                                   threadError.f0 = new AssertionError();
                               }
                           });
           thread.start();
           thread.join();
           if (threadError.f0 != null) {
               throw threadError.f0;
           }
       }
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "FAILURE",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     }, {
       "hash" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157",
       "triggerID" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 56c94ffff67b56b6e6f490103a6be681921d33da Azure: [FAILURE](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079) 
   * 7c7133b540da13a268fddabd97cc873509210f7a Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559586009



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, true);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);

Review comment:
       Yes, I addressed this issue and pushed it to your branch.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555379990



##########
File path: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
##########
@@ -527,38 +528,48 @@ protected void beforeInvoke() throws Exception {
 
 	@Override
 	public final void invoke() throws Exception {
+		// Monitor user codes from exiting JVM. This can be done in a finer-grained way like enclosing user callback
+		// functions individually (open, close, invoke, run, cancel, etc), but as Flink-managed exit is not performed
+		// and expected in this invoke function anyhow, we can monitor exiting JVM for entire scope. Cancel and
+		// checkpoint are handled in their own methods.
+		FlinkUserSecurityManager.monitorSystemExitForCurrentThread();
 		try {
-			beforeInvoke();
+			try {
+				beforeInvoke();
 
-			// final check to exit early before starting to run
-			if (canceled) {
-				throw new CancelTaskException();
-			}
+				// final check to exit early before starting to run
+				if (canceled) {
+					throw new CancelTaskException();
+				}
 
-			// let the task do its work
-			runMailboxLoop();
+				// let the task do its work
+				runMailboxLoop();
 
-			// if this left the run() method cleanly despite the fact that this was canceled,
-			// make sure the "clean shutdown" is not attempted
-			if (canceled) {
-				throw new CancelTaskException();
-			}
+				// if this left the run() method cleanly despite the fact that this was canceled,
+				// make sure the "clean shutdown" is not attempted
+				if (canceled) {
+					throw new CancelTaskException();
+				}
 
-			afterInvoke();
-		}
-		catch (Throwable invokeException) {
-			failing = !canceled;
-			try {
-				cleanUpInvoke();
-			}
-			// TODO: investigate why Throwable instead of Exception is used here.
-			catch (Throwable cleanUpException) {
-				Throwable throwable = ExceptionUtils.firstOrSuppressed(cleanUpException, invokeException);
-				ExceptionUtils.rethrowException(throwable);
+				afterInvoke();
+			} catch (Throwable invokeException) {
+				failing = !canceled;
+				try {
+					cleanUpInvoke();
+				}
+				// TODO: investigate why Throwable instead of Exception is used here.
+				catch (Throwable cleanUpException) {
+					Throwable throwable = ExceptionUtils.firstOrSuppressed(
+						cleanUpException,
+						invokeException);
+					ExceptionUtils.rethrowException(throwable);
+				}
+				ExceptionUtils.rethrowException(invokeException);
 			}
-			ExceptionUtils.rethrowException(invokeException);
+			cleanUpInvoke();
+		} finally {
+			FlinkUserSecurityManager.unmonitorSystemExitForCurrentThread();

Review comment:
       Moved the protection to `Task.doRun` to protect invoke() call.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559200445



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -115,4 +127,35 @@ public static boolean isDeclarativeResourceManagementEnabled(Configuration confi
         return configuration.get(ENABLE_DECLARATIVE_RESOURCE_MANAGEMENT)
                 && !System.getProperties().containsKey("flink.tests.disable-declarative");
     }
+
+    /** The mode of how to handle user code attempting to exit JVM. */
+    public enum UserSystemExitMode {
+        DISABLED("Flink is not monitoring or intercepting calls to System.exit()"),
+        LOG("Log exit attempt with stack trace but still allowing exit to be performed"),
+        THROW("Throw exception when exit is attempted disallowing JVM termination");
+
+        private final String description;
+
+        UserSystemExitMode(String description) {
+            this.description = description;
+        }
+
+        public static Description getConfigDescription() {
+            Description.DescriptionBuilder builder = Description.builder();
+            List<TextElement> modeDescriptions =
+                    new ArrayList<>(UserSystemExitMode.values().length);
+            builder.text(
+                    "Flag to check user code exiting system by terminating JVM (e.g., System.exit())");
+            for (UserSystemExitMode mode : UserSystemExitMode.values()) {
+                modeDescriptions.add(
+                        text(String.format("%s - %s", mode.name(), mode.getDescription())));
+            }
+            builder.list(modeDescriptions.toArray(new TextElement[modeDescriptions.size()]));

Review comment:
       >In your suggested documentation, I think HALT_ON_FATAL_ERROR should be INTERCEPT_USER_SYSTEM_EXIT.
   
   This part is obviously due to my misread of your comment. The new explanation seems clearer.
   
   >I'm also happy to not apply this suggestion. I believe we are optimizing the last 1% out of two rarely used expert features.
   
   Agreed but maybe adding this explanation may help. Added.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559580324



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,418 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitHandlerTriggered() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, exitStatus::set);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitHandlerTriggeredWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, true);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);

Review comment:
       ```suggestion
         try {
               newSecurityManager.checkExit(TEST_EXIT_CODE);
               fail("Expect exception to be thrown");
           } catch (UserSystemExitException ue) {
           }
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger closed pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger closed pull request #14499:
URL: https://github.com/apache/flink/pull/14499


   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559581503



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,418 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitHandlerTriggered() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, exitStatus::set);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitHandlerTriggeredWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, true);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(newSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)

Review comment:
       ```suggestion
       @Test
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r558408690



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {

Review comment:
       Fair enough, mentioning it in the docs should be good enough.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r557504349



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -88,21 +93,28 @@
                                     .build());
 
     @Documentation.Section(Documentation.Sections.EXPERT_CLUSTER)
-    public static final ConfigOption<Boolean> HALT_ON_FATAL_ERROR =
-            key("cluster.processes.halt-on-fatal-error")
+    public static final ConfigOption<Boolean> HALT_ON_SYSTEM_EXIT =
+            key("cluster.processes.halt-on-system-exit")

Review comment:
       I'm sorry for the back and forth on this!




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 84a20ae008df61a397e91e2238bb52afcf3e5124 Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559580324



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,418 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitHandlerTriggered() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, exitStatus::set);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitHandlerTriggeredWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, true);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);

Review comment:
       ```suggestion
           try {
               newSecurityManager.checkExit(TEST_EXIT_CODE);
               fail("Expect exception to be thrown");
           } catch (UserSystemExitException ue) {
           }
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r550363245



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+	public void checkExit(int status) {
+		super.checkExit(status);
+		if (!systemExitMonitored()) {
+			return;
+		}
+		switch (checkExitMode) {
+			case DISABLED:
+				break;
+			case WARN:
+				// Add exception trace log to help users to debug where exit came from.
+				LOG.warn("Exiting JVM with status {} is monitored, logging and exiting",
+					status, new UserSystemExitException());
+				break;
+			case THROW:
+				throw new UserSystemExitException();
+			default:
+				// Must not happen if exhaustively handling all modes above. Logging as being already at exit path.
+				LOG.warn("No valid check exit mode configured: {}", checkExitMode);
+		}
+	}
+
+	public SecurityManager getOriginalSecurityManager() {
+		return originalSecurityManager;
+	}
+
+	public void monitorSystemExit() {
+		monitorSystemExit.set(true);
+	}
+
+	public void unmonitorSystemExit() {
+		monitorSystemExit.set(false);
+	}
+
+	public boolean systemExitMonitored() {
+		return Boolean.TRUE.equals(monitorSystemExit.get());
+	}
+
+	public static void monitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();
+		if (securityManager instanceof  FlinkUserSecurityManager) {
+			((FlinkUserSecurityManager) securityManager).monitorSystemExit();
+		}
+	}
+
+	public static void unmonitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();

Review comment:
       Great point. The original patch was written long ago before while no exit security manager existed. I think we should consider this correctly. There would be two ways: 1) forcing FlinkUserSecurityManager at the end, or 2) making this manager as a singleton, so that it could be configurable regardless of where it's chained. I will take a look at how it looks like.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-756715886


   
   > > Thanks a lot for this PR. Here are some suggestions on the config parameters and the overall SecurityManager use in Flink:
   > > ```
   > > * Only set security manager if `cluster.processes.halt-on-system-exit == true && cluster.intercept-system-exit == DISABLED` (the defaults) ... to avoid performance penalties for users who don't need this.
   > > ```
   > 
   > IIUC, either halt-on-system-exit is set to true or intercept-system-exit is not disabled, security manager is set. The former is applied globally regardless of user vs. framework, while the latter is only applied for user-monitored section.
   
   Your understanding is correct.
   
   
   > > 
   > >     * This currently only works for StreamTasks? What about batch tasks? We should implement this in a way that it works for all user code.
   
   > As discussed in the Jira issue, we start with partial coverage, after which more coverage can be done incrementally with separate tickets. If I move monitoring into Task.doRun, which I will do, we can effectively monitor more coverage not just stream task.
   
   I believe that this is a fair approach! We just need to make sure that people don't have the wrong expectation from this pull request. 
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555380611



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest {

Review comment:
       Unified ExitTrappingSecurityManager to FlinkSecurityManager. Nonetheless, added multiple security managers test.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r550363393



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {

Review comment:
       Good idea. Will incorporate this comment.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,

Review comment:
       Will use LOG. Thanks.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555762014



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "
+                                        + "security manager features (e.g., '%s: %s', '%s: %s')",
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.key(),
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.defaultValue(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.key(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.defaultValue(),
+                                e));
+            }
+        }
+        FlinkSecurityManager.flinkSecurityManager = flinkSecurityManager;
+    }
+
+    public static void monitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.monitorUserSystemExit();
+        }
+    }
+
+    public static void unmonitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.unmonitorUserSystemExit();
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm);
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm, context);
+        }
+    }
+
+    @Override
+    public void checkExit(int status) {
+        if (userSystemExitMonitored()) {
+            switch (userSystemExitMode) {
+                case DISABLED:
+                    break;
+                case LOG:
+                    // Add exception trace log to help users to debug where exit came from.
+                    LOG.warn(
+                            "Exiting JVM with status {} is monitored, logging and exiting",
+                            status,
+                            new UserSystemExitException());

Review comment:
       Please note the rationale right above this log as `// Add exception trace log to help users to debug where exit came from.`. I found just logging without stack not very helpful for debugging. 




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555570200



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -115,4 +127,35 @@ public static boolean isDeclarativeResourceManagementEnabled(Configuration confi
         return configuration.get(ENABLE_DECLARATIVE_RESOURCE_MANAGEMENT)
                 && !System.getProperties().containsKey("flink.tests.disable-declarative");
     }
+
+    /** The mode of how to handle user code attempting to exit JVM. */
+    public enum UserSystemExitMode {
+        DISABLED("No check is enabled, that is allowing exit without any action"),

Review comment:
       ```suggestion
           DISABLED("No check is enabled. Hence, exits are allowed without any action being triggered."),
   ```
   I feel like the current version of the description might be ambiguous: Firstly, I read it like "there is no check which allows exiting without any action". I suggest the version above.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "
+                                        + "security manager features (e.g., '%s: %s', '%s: %s')",
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.key(),
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.defaultValue(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.key(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.defaultValue(),
+                                e));
+            }
+        }
+        FlinkSecurityManager.flinkSecurityManager = flinkSecurityManager;
+    }
+
+    public static void monitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.monitorUserSystemExit();
+        }
+    }
+
+    public static void unmonitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.unmonitorUserSystemExit();
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm);
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm, context);
+        }
+    }
+
+    @Override
+    public void checkExit(int status) {
+        if (userSystemExitMonitored()) {
+            switch (userSystemExitMode) {
+                case DISABLED:
+                    break;
+                case LOG:
+                    // Add exception trace log to help users to debug where exit came from.
+                    LOG.warn(
+                            "Exiting JVM with status {} is monitored, logging and exiting",
+                            status,
+                            new UserSystemExitException());

Review comment:
       Do we need to add this exception here? It does not add any value, doesn't it?

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, true);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);

Review comment:
       You would have to catch the `UserSystemExitException` here instead of using JUnit's `expected` feature if you want to check afterwards whether `TestExitSecurityManager` was triggered.

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, true);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {

Review comment:
       You could add this feature to the testing implementation of `SecurityManager` instead of using an anonymous class. See [TestingDispatcherRunner](https://github.com/apache/flink/blob/c6997c97c575d334679915c328792b8a3067cfb5/flink-runtime/src/test/java/org/apache/flink/runtime/dispatcher/runner/TestingDispatcherRunner.java) as an example `Testing*` implementation.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "

Review comment:
       ```suggestion
                                           + "SecurityManager to allow the permission or do not use "
   ```

##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -88,21 +93,28 @@
                                     .build());
 
     @Documentation.Section(Documentation.Sections.EXPERT_CLUSTER)
-    public static final ConfigOption<Boolean> HALT_ON_FATAL_ERROR =
-            key("cluster.processes.halt-on-fatal-error")
+    public static final ConfigOption<Boolean> HALT_ON_SYSTEM_EXIT =
+            key("cluster.processes.halt-on-system-exit")

Review comment:
       Renaming an existing parameter has a bigger impact on users as they possibly have to change their existing configuration files when upgrading. Having said that, I would be ok with that change if we decide to merge both parameters into one as they are more or less just defining some special exit behavior. What do you think?

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "
+                                        + "security manager features (e.g., '%s: %s', '%s: %s')",
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.key(),
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.defaultValue(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.key(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.defaultValue(),
+                                e));
+            }
+        }
+        FlinkSecurityManager.flinkSecurityManager = flinkSecurityManager;
+    }
+
+    public static void monitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.monitorUserSystemExit();
+        }
+    }
+
+    public static void unmonitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.unmonitorUserSystemExit();
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm);
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm, context);
+        }
+    }
+
+    @Override
+    public void checkExit(int status) {
+        if (userSystemExitMonitored()) {
+            switch (userSystemExitMode) {
+                case DISABLED:
+                    break;
+                case LOG:
+                    // Add exception trace log to help users to debug where exit came from.
+                    LOG.warn(
+                            "Exiting JVM with status {} is monitored, logging and exiting",

Review comment:
       ```suggestion
                               "Exiting JVM with status {} is monitored: The system will exit due to this call.",
   ```
   I suggest a slight modification here...

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, true);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);

Review comment:
       ```suggestion
           flinkSecurityManager = FlinkSecurityManager.fromConfiguration(new Configuration());
   ```
   You're testing the `NullPointerException` being thrown by `Configuration.set(..)`. Calling `fromConfiguration` on an empty configuration would do it. But, I guess, this test is obsolete as there is no special behavior in `fromConfiguration` that needs to get tested. It should use the default value for `ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT`.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {

Review comment:
       We have a special parameter combination where we might have to add (at least) a warning: `HALT_ON_SYSTEM_EXIT ` is implicitly disabled right now if `INTERCEPT_USER_SYSTEM_EXIT` is set to `THROW`.

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {

Review comment:
       This test is obsolete as it tests the same thing as `testThrowConfiguration` if I'm not mistaken?

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {

Review comment:
       ```suggestion
       public void testExitHandlerTriggered() {
   ```
   I'm gonna suggest the name change as "behaviorChanged" felt like it's too general to describe what the test is doing.

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, true);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(newSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetLastAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+
+        TestExitSecurityManager oldSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(oldSecurityManager);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        System.getSecurityManager().checkExit(TEST_EXIT_CODE);

Review comment:
       Here as well: You would have to catch the `UserSystemExitException` here instead of using JUnit's `expected` feature if you want to check afterwards whether `TestExitSecurityManager` was triggered.

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {

Review comment:
       We could split this test case up into three.

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;

Review comment:
       Is there a reason why we have `FlinkSecurityManager` as a member instead of local variables within each test?

##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -88,21 +93,28 @@
                                     .build());
 
     @Documentation.Section(Documentation.Sections.EXPERT_CLUSTER)
-    public static final ConfigOption<Boolean> HALT_ON_FATAL_ERROR =
-            key("cluster.processes.halt-on-fatal-error")
+    public static final ConfigOption<Boolean> HALT_ON_SYSTEM_EXIT =
+            key("cluster.processes.halt-on-system-exit")

Review comment:
       Thinking about it again, I realize that they behave in a slightly different way as the `System.exit` logic is only called in specific code regions (i.e. user code) whereas the halt logic applies anywhere. @mxm The halt logic was intended to work on the whole code base, wasn't it?

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));

Review comment:
       ```suggestion
                           status -> exitStatus::set);
   ```

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =

Review comment:
       This should be a separated test.

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {

Review comment:
       `testLogConfiguration` covers the same scenario - we could delete that test in my opinion.

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {

Review comment:
       ```suggestion
       public void testExitHandlerTriggeredWithExistingSecurityManager() {
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555770460



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;

Review comment:
       Never mind for answering my followup question. As it's more historical reason, I can change it to local variable as it seems to be more clear and member variable doesn't add convenience much now (a few typing savings).




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r552132483



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest extend TestLogger {
+
+	private static final int TEST_EXIT_CODE = 123;
+	private FlinkUserSecurityManager flinkUserSecurityManager;
+
+	@Before
+	public void setUp() {
+		flinkUserSecurityManager = new FlinkUserSecurityManager(FlinkUserSecurityManager.CheckExitMode.THROW);
+		System.setSecurityManager(flinkUserSecurityManager);
+	}
+
+	@After
+	public void tearDown() {
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager.getOriginalSecurityManager());
+		}
+	}
+
+	@Test(expected = UserSystemExitException.class)
+	public void testThrowUserExit() {
+		flinkUserSecurityManager.monitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testToggleUserExit() {
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		flinkUserSecurityManager.unmonitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testPerThreadThrowUserExit() throws Exception {
+		ExecutorService executorService = Executors.newSingleThreadExecutor();
+		// Async thread test before enabling monitoring ensures it does not throw while prestarting
+		// worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+		CompletableFuture<Void> future =
+			CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		// This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+		future = CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+	}
+
+	@Test
+	public void testInheritedThrowUserExit() throws Exception {
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		Thread thread = new Thread(() -> {
+			try {
+				flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+				fail();
+			} catch (UserSystemExitException ignored) {
+			} catch (Throwable t) {
+				fail();

Review comment:
       Ah, perfect. Thanks!




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r551760064



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest extend TestLogger {

Review comment:
       ```suggestion
   public class FlinkUserSecurityManagerTest extends TestLogger {
   ```

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,168 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * {@code FlinkUserSecurityManager} to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private final ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private final CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();

Review comment:
       ```suggestion
   ```
   According to my IDE, `super()` is not necessary here.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,168 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * {@code FlinkUserSecurityManager} to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private final ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private final CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated based on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+  @Override
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+  @Override
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+  @Override
+	public void checkExit(int status) {
+		super.checkExit(status);
+		if (!systemExitMonitored()) {
+			return;
+		}
+		switch (checkExitMode) {
+			case DISABLED:
+				break;
+			case WARN:
+				// Add exception trace log to help users to debug where exit came from.
+				LOG.warn("Exiting JVM with status {} is monitored, logging and exiting",
+					status, new UserSystemExitException());
+				break;
+			case THROW:
+				throw new UserSystemExitException();
+			default:
+				// Must not happen if exhaustively handling all modes above. Logging as being already at exit path.
+				LOG.warn("No valid check exit mode configured: {}", checkExitMode);
+		}
+	}
+
+	public SecurityManager getOriginalSecurityManager() {
+		return originalSecurityManager;
+	}
+
+	public void monitorSystemExit() {
+		monitorSystemExit.set(true);
+	}
+
+	public void unmonitorSystemExit() {
+		monitorSystemExit.set(false);
+	}
+
+	public boolean systemExitMonitored() {
+		return Boolean.TRUE.equals(monitorSystemExit.get());
+	}
+
+	public static void monitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();
+		if (securityManager instanceof  FlinkUserSecurityManager) {

Review comment:
       ```suggestion
   		if (securityManager instanceof  FlinkUserSecurityManager) {
   ```
   ```suggestion
   		if (securityManager instanceof FlinkUserSecurityManager) {
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r557493710



##########
File path: flink-clients/src/main/java/org/apache/flink/client/program/PackagedProgram.java
##########
@@ -210,7 +211,12 @@ public String getDescription() throws ProgramInvocationException {
      * local execution by default.
      */
     public void invokeInteractiveModeForExecution() throws ProgramInvocationException {
-        callMainMethod(mainClass, args);
+        FlinkUserSecurityManager.monitorSystemExitForCurrentThread();

Review comment:
       Ah, you have added this here because in some execution modes, the JobManager process is running Flink's main methods.
   Makes sense. Consider my comment resolved.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "FAILURE",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     }, {
       "hash" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 56c94ffff67b56b6e6f490103a6be681921d33da Azure: [FAILURE](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079) 
   * 7c7133b540da13a268fddabd97cc873509210f7a UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-761572560


   > I believe sources are relatively important, in particular because some users have their own implementations there.
   > @hwanju do you want to address this in this PR, or in a follow up?
   
   This is nice catch! This should be addressed as part of this PR. When this patch was written, the old Flink version did invoke `run()` in `invoke()` in the same thread, but now with mailbox approach, it spawns a separate thread for run(). Yes, `FlinkSecurityManager` supports thread-inherited monitoring flag, so why this spawned thread wasn't protected? The reason is the thread local variables are inherited at the time of thread construction, not thread run, and the construction happens in `loadAndInstantiateInvokable` before `invoke`. By wrapping `loadAndInstantiateInvokable` with exit monitoring, I see it's protected as follows:
   
   ```
   org.apache.flink.runtime.UserSystemExitException: Flink user code attempted to exit JVM.
   	at org.apache.flink.runtime.security.FlinkSecurityManager.checkExit(FlinkSecurityManager.java:184)
   	at java.lang.Runtime.exit(Runtime.java:107)
   	at java.lang.System.exit(System.java:971)
   	at org.apache.flink.streaming.tests.FailureTestSource.run(FailureTestSource.java:65)
   	at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:110)
   	at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:66)
   	at org.apache.flink.streaming.runtime.tasks.SourceStreamTask$LegacySourceFunctionThread.run(SourceStreamTask.java:245)
   ```
   
   The only thing left right now is unit test coverage. Having spent a bit of time, it doesn't seem straightforward to do StreamSourceTask testing unlike StreamTask, due to some trivial issues like serialization. It's not impossible and maybe it's just me not knowing proper test utils yet. I will try a bit more and update the PR with conclusion.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * b3f5f5084ab6af669a4f79c47977ad16bab50650 Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893) 
   * 56c94ffff67b56b6e6f490103a6be681921d33da UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r549123415



##########
File path: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
##########
@@ -527,38 +528,48 @@ protected void beforeInvoke() throws Exception {
 
 	@Override
 	public final void invoke() throws Exception {
+		// Monitor user codes from exiting JVM. This can be done in a finer-grained way like enclosing user callback
+		// functions individually (open, close, invoke, run, cancel, etc), but as Flink-managed exit is not performed
+		// and expected in this invoke function anyhow, we can monitor exiting JVM for entire scope. Cancel and
+		// checkpoint are handled in their own methods.
+		FlinkUserSecurityManager.monitorSystemExitForCurrentThread();
 		try {
-			beforeInvoke();
+			try {
+				beforeInvoke();
 
-			// final check to exit early before starting to run
-			if (canceled) {
-				throw new CancelTaskException();
-			}
+				// final check to exit early before starting to run
+				if (canceled) {
+					throw new CancelTaskException();
+				}
 
-			// let the task do its work
-			runMailboxLoop();
+				// let the task do its work
+				runMailboxLoop();
 
-			// if this left the run() method cleanly despite the fact that this was canceled,
-			// make sure the "clean shutdown" is not attempted
-			if (canceled) {
-				throw new CancelTaskException();
-			}
+				// if this left the run() method cleanly despite the fact that this was canceled,
+				// make sure the "clean shutdown" is not attempted
+				if (canceled) {
+					throw new CancelTaskException();
+				}
 
-			afterInvoke();
-		}
-		catch (Throwable invokeException) {
-			failing = !canceled;
-			try {
-				cleanUpInvoke();
-			}
-			// TODO: investigate why Throwable instead of Exception is used here.
-			catch (Throwable cleanUpException) {
-				Throwable throwable = ExceptionUtils.firstOrSuppressed(cleanUpException, invokeException);
-				ExceptionUtils.rethrowException(throwable);
+				afterInvoke();
+			} catch (Throwable invokeException) {
+				failing = !canceled;
+				try {
+					cleanUpInvoke();
+				}
+				// TODO: investigate why Throwable instead of Exception is used here.
+				catch (Throwable cleanUpException) {
+					Throwable throwable = ExceptionUtils.firstOrSuppressed(
+						cleanUpException,
+						invokeException);
+					ExceptionUtils.rethrowException(throwable);
+				}
+				ExceptionUtils.rethrowException(invokeException);
 			}
-			ExceptionUtils.rethrowException(invokeException);
+			cleanUpInvoke();
+		} finally {
+			FlinkUserSecurityManager.unmonitorSystemExitForCurrentThread();

Review comment:
       Adding some explanation here, we started with `StreamTask.invoke` protection. Before in 1.6/1.8, the patch was clean but now with the latest version of invoke, patch isn't clean much like this. The first try (to avoid indentation) was to simply add finally at the end with existing try/catch block, but we also would like to protect against `cleanUpInvoke`, where user-defined dispose/close is called. The second is making this existing body as invokeInternal, which is then wrapped with monitor system exit to avoid indentation in the patch. I didn't yet do that as that's still a little intrusive. The third option is to protect all invoke() at generic caller level (i.e., `invokable.invoke()` in `Task.doRun()`) not just for StreamTask. But there is some another caller of invoke other than Task.doRun() - currently `BoundedOneInputStreamTaskRunner.mapPartition` (but maybe missing this could be fine in the first version). I actually became preferring the third option as it's cleaner an
 d covers not just StreamTask, but I am leaving this as is currently to hear about any opinion. 




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r558359969



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -115,4 +127,35 @@ public static boolean isDeclarativeResourceManagementEnabled(Configuration confi
         return configuration.get(ENABLE_DECLARATIVE_RESOURCE_MANAGEMENT)
                 && !System.getProperties().containsKey("flink.tests.disable-declarative");
     }
+
+    /** The mode of how to handle user code attempting to exit JVM. */
+    public enum UserSystemExitMode {
+        DISABLED("Flink is not monitoring or intercepting calls to System.exit()"),
+        LOG("Log exit attempt with stack trace but still allowing exit to be performed"),
+        THROW("Throw exception when exit is attempted disallowing JVM termination");
+
+        private final String description;
+
+        UserSystemExitMode(String description) {
+            this.description = description;
+        }
+
+        public static Description getConfigDescription() {
+            Description.DescriptionBuilder builder = Description.builder();
+            List<TextElement> modeDescriptions =
+                    new ArrayList<>(UserSystemExitMode.values().length);
+            builder.text(
+                    "Flag to check user code exiting system by terminating JVM (e.g., System.exit())");
+            for (UserSystemExitMode mode : UserSystemExitMode.values()) {
+                modeDescriptions.add(
+                        text(String.format("%s - %s", mode.name(), mode.getDescription())));
+            }
+            builder.list(modeDescriptions.toArray(new TextElement[modeDescriptions.size()]));

Review comment:
       Mh, that you are asking these questions makes me doubt whether this change is necessary.
   Let me change my suggestion to
   ```java
               builder.list(modeDescriptions.toArray(new TextElement[modeDescriptions.size()]));
               builder.linebreak();
               builder.text(
                       "Note that this configuration option can interfere with %s: In intercepted user-code, a call to System.exit() will not cause the JVM to halt, when %s is configured.",
                       code(HALT_ON_FATAL_ERROR.key()), code(THROW.name()));
   ```
   Is it clearer now?
   
   I'm also happy to not apply this suggestion. I believe we are optimizing the last 1% out of two rarely used expert features.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555768435



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "
+                                        + "security manager features (e.g., '%s: %s', '%s: %s')",
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.key(),
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.defaultValue(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.key(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.defaultValue(),
+                                e));
+            }
+        }
+        FlinkSecurityManager.flinkSecurityManager = flinkSecurityManager;
+    }
+
+    public static void monitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.monitorUserSystemExit();
+        }
+    }
+
+    public static void unmonitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.unmonitorUserSystemExit();
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm);
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm, context);
+        }
+    }
+
+    @Override
+    public void checkExit(int status) {
+        if (userSystemExitMonitored()) {
+            switch (userSystemExitMode) {
+                case DISABLED:
+                    break;
+                case LOG:
+                    // Add exception trace log to help users to debug where exit came from.
+                    LOG.warn(
+                            "Exiting JVM with status {} is monitored, logging and exiting",
+                            status,
+                            new UserSystemExitException());
+                    break;
+                case THROW:
+                    throw new UserSystemExitException();
+                default:
+                    // Must not happen if exhaustively handling all modes above. Logging as being
+                    // already at exit path.
+                    LOG.warn("No valid check exit mode configured: {}", userSystemExitMode);
+            }
+        }
+        // As this security manager is current at outer most of the chain and it has exit guard
+        // option, invoke inner security manager here after passing guard checking above, if any.
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkExit(status);
+        }
+        // At this point, exit is determined. Invoke exit handler if defined, otherwise check ended.
+        if (onExitBehavior != null) {
+            // Unset ourselves to allow exiting.
+            System.setSecurityManager(null);
+            // Execute the desired behavior, e.g. forceful exit instead of proceeding with
+            // System.exit().
+            onExitBehavior.accept(status);

Review comment:
       I had the same decision point, but I rather preserved the existing implementation for the existing tests as well.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-752619323


   I'm going to add the remarks on where System.exit handling should be added here separately:
   
   Two other locations @rmetzger mentioned in [FLINK-15156](https://issues.apache.org/jira/browse/FLINK-15156) were:
   * [ExecutionGraph.vertexFinished:1149](https://github.com/apache/flink/blob/c6997c97c575d334679915c328792b8a3067cfb5/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraph.java#L1149) where it calls `finalizeOnMaster(..)` on each `JobVertex`
   * [ExecutionGraphBuilder.buildGraph:222](https://github.com/apache/flink/blob/c6997c97c575d334679915c328792b8a3067cfb5/flink-runtime/src/main/java/org/apache/flink/runtime/executiongraph/ExecutionGraphBuilder.java#L222) calls `initializeOnMaster(..)` on each `JobVertex`
   I might not have the full understanding of the call hierarchies: Are these two locations covered by your current version of the code?
   
   @rmetzger May you have another look?
   
   Thanks again for your efforts @hwanju .


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555381940



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest extend TestLogger {
+
+	private static final int TEST_EXIT_CODE = 123;
+	private FlinkUserSecurityManager flinkUserSecurityManager;
+
+	@Before
+	public void setUp() {
+		flinkUserSecurityManager = new FlinkUserSecurityManager(FlinkUserSecurityManager.CheckExitMode.THROW);
+		System.setSecurityManager(flinkUserSecurityManager);
+	}
+
+	@After
+	public void tearDown() {
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager.getOriginalSecurityManager());
+		}
+	}
+
+	@Test(expected = UserSystemExitException.class)
+	public void testThrowUserExit() {
+		flinkUserSecurityManager.monitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testToggleUserExit() {
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		flinkUserSecurityManager.unmonitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testPerThreadThrowUserExit() throws Exception {
+		ExecutorService executorService = Executors.newSingleThreadExecutor();
+		// Async thread test before enabling monitoring ensures it does not throw while prestarting
+		// worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+		CompletableFuture<Void> future =
+			CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		// This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+		future = CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+	}
+
+	@Test
+	public void testInheritedThrowUserExit() throws Exception {
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		Thread thread = new Thread(() -> {
+			try {
+				flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+				fail();
+			} catch (UserSystemExitException ignored) {
+			} catch (Throwable t) {
+				fail();

Review comment:
       Thanks @zentol and I followed your suggestion.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-754770469


   Thanks for the review! I've mostly addressed @XComp but we seem to agree on unifying security managers, so I should work on it along with changed configurations.
   
   > Thanks a lot for this PR. Here are some suggestions on the config parameters and the overall SecurityManager use in Flink:
   > 
   >     * Only set security manager if `cluster.processes.halt-on-system-exit == true && cluster.intercept-system-exit == DISABLED` (the defaults) ... to avoid performance penalties for users who don't need this.
   
   IIUC, either halt-on-system-exit is set to true or intercept-system-exit is not disabled, security manager is set. The former is applied globally regardless of user vs. framework, while the latter is only applied for user-monitored section.
   
   > 
   >     * Rename `cluster.processes.halt-on-fatal-error` to `cluster.processes.halt-on-system-exit` . This configuration option globally changes the behavior from exit to halt. If configured to true, always halt() instead of system.exit. Remove ExitTrappingSecurityManager.
   > 
   >     * Rename `security.check-system-exit` to `cluster.intercept-user-system-exit` (options: disabled, log, throw).
   > 
   >     * Rename `FlinkUserSecurityManager` to `FlinkSecurityManager`.
   > 
   > 
   > ... I'm concerned that the PR as-is makes the codebase unnecessarily complicated (with two security managers overwriting each other (`cluster.processes.halt-on-fatal-error` has no effect anymore))
   > 
   > cc @mxm, if you want to contribute to this discussion.
   
   Will unify those into FlinkSecurityManager.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r551809166



##########
File path: flink-clients/src/main/java/org/apache/flink/client/program/PackagedProgram.java
##########
@@ -210,7 +211,12 @@ public String getDescription() throws ProgramInvocationException {
      * local execution by default.
      */
     public void invokeInteractiveModeForExecution() throws ProgramInvocationException {
-        callMainMethod(mainClass, args);
+        FlinkUserSecurityManager.monitorSystemExitForCurrentThread();

Review comment:
       There won't be a `FlinkUserSecurityManager` configured if the `CliFrontend` is used.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751476681


   Thanks a lot for your contribution to the Apache Flink project. I'm the @flinkbot. I help the community
   to review your pull request. We will use this comment to track the progress of the review.
   
   
   ## Automated Checks
   Last check on commit 6bd776d5be680b296e2ab125482fa6bbb63f3c6b (Fri May 28 07:10:18 UTC 2021)
   
   **Warnings:**
    * No documentation files were touched! Remember to keep the Flink docs up to date!
   
   
   <sub>Mention the bot in a comment to re-run the automated checks.</sub>
   ## Review Progress
   
   * ❓ 1. The [description] looks good.
   * ❓ 2. There is [consensus] that the contribution should go into to Flink.
   * ❓ 3. Needs [attention] from.
   * ❓ 4. The change fits into the overall [architecture].
   * ❓ 5. Overall code [quality] is good.
   
   Please see the [Pull Request Review Guide](https://flink.apache.org/contributing/reviewing-prs.html) for a full explanation of the review process.<details>
    The Bot is tracking the review progress through labels. Labels are applied according to the order of the review items. For consensus, approval by a Flink committer of PMC member is required <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot approve description` to approve one or more aspects (aspects: `description`, `consensus`, `architecture` and `quality`)
    - `@flinkbot approve all` to approve all aspects
    - `@flinkbot approve-until architecture` to approve everything until `architecture`
    - `@flinkbot attention @username1 [@username2 ..]` to require somebody's attention
    - `@flinkbot disapprove architecture` to remove an approval you gave earlier
   </details>


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r551774866



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/SecurityOptions.java
##########
@@ -490,4 +490,25 @@
                                     + "channel. If the `close_notify` was not flushed in the given timeout the channel will be closed "
                                     + "forcibly. (-1 = use system default)")
                     .withDeprecatedKeys("security.ssl.close-notify-flush-timeout");
+
+    // ------------------------ security manager --------------------------------
+
+    /** Flag to check the attempt of main or user-defined function to terminate JVM. */
+    @Documentation.Section(Documentation.Sections.EXPERT_DEBUGGING_AND_TUNING)
+    public static final ConfigOption<String> CHECK_SYSTEM_EXIT =
+            key("security.check-system-exit")

Review comment:
       As commented already, I suggest to move this to the `cluster.` config namespace. I don't see this as a security setting, rather controlling the behavior of the process.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555674714



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkSecurityManager.java
##########
@@ -0,0 +1,220 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.annotation.VisibleForTesting;
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.Preconditions;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.security.Permission;
+import java.util.function.Consumer;
+
+/**
+ * {@code FlinkSecurityManager} to control certain behaviors that can be captured by Java system
+ * security manager. It can be used to control unexpected user behaviors that potentially impact
+ * cluster availability, for example, it can warn or prevent user code from terminating JVM by
+ * System.exit or halt by logging or throwing an exception. This does not necessarily prevent
+ * malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent
+ * unavailability.
+ */
+public class FlinkSecurityManager extends SecurityManager {
+
+    static final Logger LOG = LoggerFactory.getLogger(FlinkSecurityManager.class);
+
+    /**
+     * Security manager reference lastly set to system's security manager by public API. As system
+     * security manager can be reset with another but still chain-called into this manager properly,
+     * this reference may not be referenced by System.getSecurityManager, but we still need to
+     * control runtime check behaviors such as monitoring exit from user code.
+     */
+    private static FlinkSecurityManager flinkSecurityManager;
+
+    private final SecurityManager originalSecurityManager;
+    private final ThreadLocal<Boolean> monitorUserSystemExit = new InheritableThreadLocal<>();
+    private final ClusterOptions.UserSystemExitMode userSystemExitMode;
+
+    /** The behavior to execute when the JVM exists. */
+    private final Consumer<Integer> onExitBehavior;
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior) {
+        this(userSystemExitMode, onExitBehavior, System.getSecurityManager());
+    }
+
+    @VisibleForTesting
+    FlinkSecurityManager(
+            ClusterOptions.UserSystemExitMode userSystemExitMode,
+            @Nullable Consumer<Integer> onExitBehavior,
+            SecurityManager originalSecurityManager) {
+        this.userSystemExitMode = Preconditions.checkNotNull(userSystemExitMode);
+        this.onExitBehavior = onExitBehavior;
+        this.originalSecurityManager = originalSecurityManager;
+    }
+
+    /**
+     * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+     * check is needed, so that a caller can skip setting security manager avoiding runtime check
+     * cost, if there is no security check set up already. Use {@link #setFromConfiguration} helper,
+     * which handles disabled case.
+     *
+     * @param configuration to instantiate the security manager from
+     * @return FlinkUserSecurityManager instantiated based on configuration. Return null if
+     *     disabled.
+     */
+    @VisibleForTesting
+    static FlinkSecurityManager fromConfiguration(Configuration configuration) {
+        final ClusterOptions.UserSystemExitMode userSystemExitMode =
+                configuration.get(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT);
+
+        boolean haltOnSystemExit = configuration.get(ClusterOptions.HALT_ON_SYSTEM_EXIT);
+
+        // If no check is needed, return null so that caller can avoid setting security manager not
+        // to incur any runtime cost.
+        if (userSystemExitMode == ClusterOptions.UserSystemExitMode.DISABLED && !haltOnSystemExit) {
+            return null;
+        }
+        Consumer<Integer> onExitBehavior = null;
+        // If halt on system exit is configured, registers a custom SecurityManager which converts
+        // graceful exists calls using {@code System#exit} into forceful exit calls using
+        // {@code Runtime#halt}. The latter does not perform a clean shutdown using the registered
+        // shutdown hooks. This may be configured to prevent deadlocks with Java 8 and the G1
+        // garbage collection, see https://issues.apache.org/jira/browse/FLINK-16510.
+        if (haltOnSystemExit) {
+            onExitBehavior = status -> Runtime.getRuntime().halt(status);
+        }
+        LOG.info(
+                "FlinkSecurityManager is created with {} user system exit mode and {} exit",
+                userSystemExitMode,
+                haltOnSystemExit ? "forceful" : "graceful");
+        // Add more configuration parameters that need user security manager (currently only for
+        // system exit).
+        return new FlinkSecurityManager(userSystemExitMode, onExitBehavior);
+    }
+
+    public static void setFromConfiguration(Configuration configuration) {
+        final FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        if (flinkSecurityManager != null) {
+            try {
+                System.setSecurityManager(flinkSecurityManager);
+            } catch (Exception e) {
+                throw new IllegalConfigurationException(
+                        String.format(
+                                "Could not register security manager due to no permission to "
+                                        + "set a SecurityManager. Either update your existing "
+                                        + "SecurityManager to allow the permission or not using "
+                                        + "security manager features (e.g., '%s: %s', '%s: %s')",
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.key(),
+                                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT.defaultValue(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.key(),
+                                ClusterOptions.HALT_ON_SYSTEM_EXIT.defaultValue(),
+                                e));
+            }
+        }
+        FlinkSecurityManager.flinkSecurityManager = flinkSecurityManager;
+    }
+
+    public static void monitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.monitorUserSystemExit();
+        }
+    }
+
+    public static void unmonitorUserSystemExitForCurrentThread() {
+        if (FlinkSecurityManager.flinkSecurityManager != null) {
+            FlinkSecurityManager.flinkSecurityManager.unmonitorUserSystemExit();
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm);
+        }
+    }
+
+    @Override
+    public void checkPermission(Permission perm, Object context) {
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkPermission(perm, context);
+        }
+    }
+
+    @Override
+    public void checkExit(int status) {
+        if (userSystemExitMonitored()) {
+            switch (userSystemExitMode) {
+                case DISABLED:
+                    break;
+                case LOG:
+                    // Add exception trace log to help users to debug where exit came from.
+                    LOG.warn(
+                            "Exiting JVM with status {} is monitored, logging and exiting",
+                            status,
+                            new UserSystemExitException());
+                    break;
+                case THROW:
+                    throw new UserSystemExitException();
+                default:
+                    // Must not happen if exhaustively handling all modes above. Logging as being
+                    // already at exit path.
+                    LOG.warn("No valid check exit mode configured: {}", userSystemExitMode);
+            }
+        }
+        // As this security manager is current at outer most of the chain and it has exit guard
+        // option, invoke inner security manager here after passing guard checking above, if any.
+        if (originalSecurityManager != null) {
+            originalSecurityManager.checkExit(status);
+        }
+        // At this point, exit is determined. Invoke exit handler if defined, otherwise check ended.
+        if (onExitBehavior != null) {
+            // Unset ourselves to allow exiting.
+            System.setSecurityManager(null);
+            // Execute the desired behavior, e.g. forceful exit instead of proceeding with
+            // System.exit().
+            onExitBehavior.accept(status);

Review comment:
       I was wondering the same as the halt is the only functionality, we use so far. It would make testing harder, though.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-761008849


   During my manual test, it seems that I can intercept a System.exit() in user code of a batch job:
   
   ```
   2021-01-15 16:16:01,950 WARN  org.apache.flink.runtime.security.FlinkSecurityManager       [] - Exiting JVM with status 0 is monitored: The system will exit due to this call.
   org.apache.flink.runtime.UserSystemExitException: Flink user code attempted to exit JVM.
           at org.apache.flink.runtime.security.FlinkSecurityManager.checkExit(FlinkSecurityManager.java:180) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at java.lang.Runtime.exit(Runtime.java:107) [?:1.8.0_252]
           at java.lang.System.exit(System.java:973) [?:1.8.0_252]
           at de.robertmetzger.BatchJob$1.map(BatchJob.java:51) [blob_p-f236836ff40f8dac63909ec8c69fb02a0823541c-6253f79b4f1453ea0b6abe7b75c2857e:?]
           at de.robertmetzger.BatchJob$1.map(BatchJob.java:46) [blob_p-f236836ff40f8dac63909ec8c69fb02a0823541c-6253f79b4f1453ea0b6abe7b75c2857e:?]
           at org.apache.flink.runtime.operators.chaining.ChainedMapDriver.collect(ChainedMapDriver.java:78) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.runtime.operators.util.metrics.CountingCollector.collect(CountingCollector.java:35) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.runtime.operators.DataSourceTask.invoke(DataSourceTask.java:204) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.runtime.taskmanager.Task.doRun(Task.java:762) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.runtime.taskmanager.Task.run(Task.java:571) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at java.lang.Thread.run(Thread.java:748) [?:1.8.0_252]
   
   ```
   
   For streaming jobs, intercepting regular operators (such as map), works fine as well. 
   
   However for sources, it won't work.
   The StackTrace to a source is the following:
   ```
   2021-01-15 16:21:45,859 INFO  de.robertmetzger.StreamingJob                                [] - I'M here on the Taskmanager
   java.lang.RuntimeException: null
           at de.robertmetzger.StreamingJob$2.map(StreamingJob.java:70) [blob_p-eb6613e604b074ad283e611328dcbf036a1f86cb-4d496ef1ec5656acaa10612f41dc4839:?]
           at de.robertmetzger.StreamingJob$2.map(StreamingJob.java:67) [blob_p-eb6613e604b074ad283e611328dcbf036a1f86cb-4d496ef1ec5656acaa10612f41dc4839:?]
           at org.apache.flink.streaming.api.operators.StreamMap.processElement(StreamMap.java:38) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.runtime.tasks.CopyingChainingOutput.pushToOperator(CopyingChainingOutput.java:71) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.runtime.tasks.CopyingChainingOutput.collect(CopyingChainingOutput.java:46) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.runtime.tasks.CopyingChainingOutput.collect(CopyingChainingOutput.java:26) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.api.operators.CountingOutput.collect(CountingOutput.java:50) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.api.operators.CountingOutput.collect(CountingOutput.java:28) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.api.operators.StreamSourceContexts$ManualWatermarkContext.processAndCollect(StreamSourceContexts.java:317) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.api.operators.StreamSourceContexts$WatermarkContext.collect(StreamSourceContexts.java:411) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at de.robertmetzger.StreamingJob$1.run(StreamingJob.java:57) [blob_p-eb6613e604b074ad283e611328dcbf036a1f86cb-4d496ef1ec5656acaa10612f41dc4839:?]
           at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:110) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.api.operators.StreamSource.run(StreamSource.java:66) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
           at org.apache.flink.streaming.runtime.tasks.SourceStreamTask$LegacySourceFunctionThread.run(SourceStreamTask.java:245) [flink-dist_2.11-1.13-SNAPSHOT.jar:1.13-SNAPSHOT]
   ```
   
   I believe sources are relatively important, in particular because some users have their own implementations there.
   @hwanju do you want to address this in this PR, or in a follow up?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559580769



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,418 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitHandlerTriggered() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, exitStatus::set);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitHandlerTriggeredWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_FATAL_ERROR, true);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        FlinkSecurityManager flinkSecurityManager =
+                FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        FlinkSecurityManager flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)

Review comment:
       ```suggestion
       @Test
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-756715886


   
   > > Thanks a lot for this PR. Here are some suggestions on the config parameters and the overall SecurityManager use in Flink:
   > > ```
   > > * Only set security manager if `cluster.processes.halt-on-system-exit == true && cluster.intercept-system-exit == DISABLED` (the defaults) ... to avoid performance penalties for users who don't need this.
   > > ```
   > 
   > IIUC, either halt-on-system-exit is set to true or intercept-system-exit is not disabled, security manager is set. The former is applied globally regardless of user vs. framework, while the latter is only applied for user-monitored section.
   
   Your understanding is correct.
   
   
   > > 
   > >     * This currently only works for StreamTasks? What about batch tasks? We should implement this in a way that it works for all user code.
   
   > As discussed in the Jira issue, we start with partial coverage, after which more coverage can be done incrementally with separate tickets. If I move monitoring into Task.doRun, which I will do, we can effectively monitor more coverage not just stream task.
   
   I believe that this is a fair approach! We just need to make sure that people don't have the wrong expectation from this pull request. 
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r557495796



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -115,4 +127,35 @@ public static boolean isDeclarativeResourceManagementEnabled(Configuration confi
         return configuration.get(ENABLE_DECLARATIVE_RESOURCE_MANAGEMENT)
                 && !System.getProperties().containsKey("flink.tests.disable-declarative");
     }
+
+    /** The mode of how to handle user code attempting to exit JVM. */
+    public enum UserSystemExitMode {
+        DISABLED("No check is enabled, that is allowing exit without any action"),

Review comment:
       How about `Flink is not monitoring or intercepting calls to System.exit()`? 




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "FAILURE",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 56c94ffff67b56b6e6f490103a6be681921d33da Azure: [FAILURE](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r550281920



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());

Review comment:
       Thanks for the suggestion! I did not know config has enumType and thus followed existing way of `security.ssl.provide`. That's indeed what I was looking for, so let me change it to enumType as you suggest.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r551828818



##########
File path: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
##########
@@ -527,38 +528,48 @@ protected void beforeInvoke() throws Exception {
 
 	@Override
 	public final void invoke() throws Exception {
+		// Monitor user codes from exiting JVM. This can be done in a finer-grained way like enclosing user callback
+		// functions individually (open, close, invoke, run, cancel, etc), but as Flink-managed exit is not performed
+		// and expected in this invoke function anyhow, we can monitor exiting JVM for entire scope. Cancel and
+		// checkpoint are handled in their own methods.
+		FlinkUserSecurityManager.monitorSystemExitForCurrentThread();
 		try {
-			beforeInvoke();
+			try {
+				beforeInvoke();
 
-			// final check to exit early before starting to run
-			if (canceled) {
-				throw new CancelTaskException();
-			}
+				// final check to exit early before starting to run
+				if (canceled) {
+					throw new CancelTaskException();
+				}
 
-			// let the task do its work
-			runMailboxLoop();
+				// let the task do its work
+				runMailboxLoop();
 
-			// if this left the run() method cleanly despite the fact that this was canceled,
-			// make sure the "clean shutdown" is not attempted
-			if (canceled) {
-				throw new CancelTaskException();
-			}
+				// if this left the run() method cleanly despite the fact that this was canceled,
+				// make sure the "clean shutdown" is not attempted
+				if (canceled) {
+					throw new CancelTaskException();
+				}
 
-			afterInvoke();
-		}
-		catch (Throwable invokeException) {
-			failing = !canceled;
-			try {
-				cleanUpInvoke();
-			}
-			// TODO: investigate why Throwable instead of Exception is used here.
-			catch (Throwable cleanUpException) {
-				Throwable throwable = ExceptionUtils.firstOrSuppressed(cleanUpException, invokeException);
-				ExceptionUtils.rethrowException(throwable);
+				afterInvoke();
+			} catch (Throwable invokeException) {
+				failing = !canceled;
+				try {
+					cleanUpInvoke();
+				}
+				// TODO: investigate why Throwable instead of Exception is used here.
+				catch (Throwable cleanUpException) {
+					Throwable throwable = ExceptionUtils.firstOrSuppressed(
+						cleanUpException,
+						invokeException);
+					ExceptionUtils.rethrowException(throwable);
+				}
+				ExceptionUtils.rethrowException(invokeException);
 			}
-			ExceptionUtils.rethrowException(invokeException);
+			cleanUpInvoke();
+		} finally {
+			FlinkUserSecurityManager.unmonitorSystemExitForCurrentThread();

Review comment:
       Missing the call in `BoundedOneInputStreamTaskRunner.mapPartition` is fine. It is not used at runtime. Just by the state processing API framework (which allows users to read / write Flink state from savepoints).
   
   `Task.doRun()` seems like a good code location to me.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] zentol commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
zentol commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r552126275



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest extend TestLogger {
+
+	private static final int TEST_EXIT_CODE = 123;
+	private FlinkUserSecurityManager flinkUserSecurityManager;
+
+	@Before
+	public void setUp() {
+		flinkUserSecurityManager = new FlinkUserSecurityManager(FlinkUserSecurityManager.CheckExitMode.THROW);
+		System.setSecurityManager(flinkUserSecurityManager);
+	}
+
+	@After
+	public void tearDown() {
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager.getOriginalSecurityManager());
+		}
+	}
+
+	@Test(expected = UserSystemExitException.class)
+	public void testThrowUserExit() {
+		flinkUserSecurityManager.monitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testToggleUserExit() {
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		flinkUserSecurityManager.unmonitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testPerThreadThrowUserExit() throws Exception {
+		ExecutorService executorService = Executors.newSingleThreadExecutor();
+		// Async thread test before enabling monitoring ensures it does not throw while prestarting
+		// worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+		CompletableFuture<Void> future =
+			CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		// This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+		future = CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+	}
+
+	@Test
+	public void testInheritedThrowUserExit() throws Exception {
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		Thread thread = new Thread(() -> {
+			try {
+				flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+				fail();
+			} catch (UserSystemExitException ignored) {
+			} catch (Throwable t) {
+				fail();

Review comment:
       You can use a `CheckedThread` instead.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * b3f5f5084ab6af669a4f79c47977ad16bab50650 Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893) 
   * 56c94ffff67b56b6e6f490103a6be681921d33da Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     }, {
       "hash" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157",
       "triggerID" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "triggerType" : "PUSH"
     }, {
       "hash" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12186",
       "triggerID" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "triggerType" : "PUSH"
     }, {
       "hash" : "6bd776d5be680b296e2ab125482fa6bbb63f3c6b",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12189",
       "triggerID" : "6bd776d5be680b296e2ab125482fa6bbb63f3c6b",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 359060d80001a5406849d8c7f4b8c58344238ed1 Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12186) 
   * 6bd776d5be680b296e2ab125482fa6bbb63f3c6b Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12189) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "FAILURE",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 3ea63637fe7a8c46e434fe3661fe9df444e389a3 Azure: [FAILURE](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "FAILURE",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 03e163b7c14a96058f14dd003cbffe8844a78f85 Azure: [FAILURE](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r550073467



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());

Review comment:
       I'd suggest moving the parameter parsing into the `SecurityOptions` instead of dealing with it here. That could be achieved by making `SecurityOptions.CHECK_SYSTEM_EXIT` an enum-typed `ConfigOption`.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.

Review comment:
       ```suggestion
   	 * @return FlinkUserSecurityManager instantiated based on configuration. Return null if disabled.
   ```

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,

Review comment:
       I would suggest renaming `WARN` as it might be too generic. Something like `LOG` or `LOG_WARNING` would be more explicit in what channel is used for the warning.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;

Review comment:
       ```suggestion
   	private final ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
   	private final CheckExitMode checkExitMode;
   ```
   Both fields `monitorSystemExit` and `checkExitMode` can be marked as `final` as they are not changed anymore.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for

Review comment:
       ```suggestion
    * {@code FlinkUserSecurityManager} to control unexpected user behaviors that potentially impact cluster availability, for
   ```
   Minor detail: I suggest using the class name as is since we're referring to it here.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+	public void checkPermission(Permission perm, Object context) {

Review comment:
       ```suggestion
     @Override
   	public void checkPermission(Permission perm, Object context) {
   ```

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {

Review comment:
       ```suggestion
     @Override
   	public void checkPermission(Permission perm) {
   ```

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {

Review comment:
       What about adding the description as a field to the enum. This way, you could even use it in the `SecurityOptions`' `ConfigOption` definition and we avoid having the same information available twice.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+	public void checkExit(int status) {

Review comment:
       We're not calling `checkExit(int)` on the `originalSecurityManager` here. There's another `SecurityManager` implemented in the context of [FLINK-16510](https://issues.apache.org/jira/browse/FLINK-16510) called `ExitTrappingSecurityManager`. Its `checkExit` is not called if both `SecurityManager`s are configured, if I'm not mistaken.

##########
File path: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
##########
@@ -705,9 +715,11 @@ public final void cancel() throws Exception {
 
         // the "cancel task" call must come first, but the cancelables must be
         // closed no matter what
+        FlinkUserSecurityManager.monitorSystemExitForCurrentThread();
         try {
             cancelTask();

Review comment:
       Just for documentation purposes: `cancelTask` indirectly calls `SourceFunction.cancel()`

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+	public void checkExit(int status) {
+		super.checkExit(status);
+		if (!systemExitMonitored()) {
+			return;
+		}
+		switch (checkExitMode) {
+			case DISABLED:
+				break;
+			case WARN:
+				// Add exception trace log to help users to debug where exit came from.
+				LOG.warn("Exiting JVM with status {} is monitored, logging and exiting",
+					status, new UserSystemExitException());
+				break;
+			case THROW:
+				throw new UserSystemExitException();
+			default:
+				// Must not happen if exhaustively handling all modes above. Logging as being already at exit path.
+				LOG.warn("No valid check exit mode configured: {}", checkExitMode);
+		}
+	}
+
+	public SecurityManager getOriginalSecurityManager() {
+		return originalSecurityManager;
+	}
+
+	public void monitorSystemExit() {
+		monitorSystemExit.set(true);
+	}
+
+	public void unmonitorSystemExit() {
+		monitorSystemExit.set(false);
+	}
+
+	public boolean systemExitMonitored() {
+		return Boolean.TRUE.equals(monitorSystemExit.get());
+	}
+
+	public static void monitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();
+		if (securityManager instanceof  FlinkUserSecurityManager) {
+			((FlinkUserSecurityManager) securityManager).monitorSystemExit();
+		}
+	}
+
+	public static void unmonitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();

Review comment:
       Here we have to be careful considering that multiple `SecurityManagers` can be set wrapped up within each other. This code might not enabling/disabling might not work depending on the `SecurityManager` initialization order.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);

Review comment:
       We might want to add more error handling here as well: Compare with `ExitTrappingSecurityManager.replaceGracefulExitWithHaltIfConfigured(..)`

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest {

Review comment:
       Does it make sense to check how the code behaves when there's already a `SecurityManager` initialized? ...considering that we have more than one `SecurityManager` that can be configured.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/entrypoint/ClusterEntrypoint.java
##########
@@ -220,6 +221,7 @@ private SecurityContext installSecurityContext(Configuration configuration) thro
     private void runCluster(Configuration configuration, PluginManager pluginManager)
             throws Exception {
         synchronized (lock) {
+            FlinkUserSecurityManager.setFromConfiguration(configuration);

Review comment:
       Here, we might want to collect all SecurityManager initialization calls into a single method to have it in the same place. `ExitTrappingSecurityManager` might be initialized within `ClusterEntrypoint.startCluster`. This is also the order I referred to in my comment in `FlinkUserSecurityManager. unmonitorSystemExitForCurrentThread`.

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest {
+
+	private static final int TEST_EXIT_CODE = 123;
+	private FlinkUserSecurityManager flinkUserSecurityManager;
+
+	@Before
+	public void setUp() {
+		flinkUserSecurityManager = new FlinkUserSecurityManager(FlinkUserSecurityManager.CheckExitMode.THROW);
+		System.setSecurityManager(flinkUserSecurityManager);
+	}
+
+	@After
+	public void tearDown() {
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager.getOriginalSecurityManager());
+		}
+	}
+
+	@Test(expected = UserSystemExitException.class)
+	public void testThrowUserExit() {
+		flinkUserSecurityManager.monitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testToggleUserExit() {
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		flinkUserSecurityManager.unmonitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testPerThreadThrowUserExit() throws Exception {
+		ExecutorService executorService = Executors.newSingleThreadExecutor();
+		// Async thread test before enabling monitoring ensures it does not throw while prestarting
+		// worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+		CompletableFuture<Void> future =
+			CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		// This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+		future = CompletableFuture.runAsync(() -> flinkUserSecurityManager.checkExit(TEST_EXIT_CODE),
+				executorService);
+		future.get();
+	}
+
+	@Test
+	public void testInheritedThrowUserExit() throws Exception {
+		flinkUserSecurityManager.monitorSystemExit();
+		try {
+			flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+			fail();
+		} catch (UserSystemExitException ignored) { }
+		Thread thread = new Thread(() -> {
+			try {
+				flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+				fail();
+			} catch (UserSystemExitException ignored) {
+			} catch (Throwable t) {
+				fail();
+			}
+		});
+		thread.start();
+		thread.join();
+	}
+
+	@Test
+	public void testWarnUserExit() {
+		// Warn mode enables monitor but only logging allowing exit, hence not expecting exception.
+		// NOTE - Do not specifically test warning logging.
+		System.setSecurityManager(flinkUserSecurityManager.getOriginalSecurityManager());
+		flinkUserSecurityManager = new FlinkUserSecurityManager(FlinkUserSecurityManager.CheckExitMode.WARN);
+		System.setSecurityManager(flinkUserSecurityManager);
+		flinkUserSecurityManager.monitorSystemExit();
+		flinkUserSecurityManager.checkExit(TEST_EXIT_CODE);
+	}
+
+	@Test
+	public void testValidConfiguration() {

Review comment:
       Could we split this up into multiple test cases/methods to improve readability?

##########
File path: flink-streaming-java/src/test/java/org/apache/flink/streaming/runtime/tasks/StreamTaskSystemExitTest.java
##########
@@ -0,0 +1,130 @@
+/*
+ * 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.flink.streaming.runtime.tasks;
+
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.runtime.execution.Environment;
+import org.apache.flink.runtime.operators.testutils.MockEnvironmentBuilder;
+import org.apache.flink.runtime.security.FlinkUserSecurityManager;
+import org.apache.flink.streaming.api.operators.AbstractStreamOperator;
+import org.apache.flink.streaming.runtime.tasks.mailbox.MailboxDefaultAction;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Tests for stream task where user-invokable codes try to exit JVM.
+ * Currently, monitoring system exit is enabled inside relevant methods that can call user-defined
+ * functions in {@code StreamTask}.
+ */
+public class StreamTaskSystemExitTest {

Review comment:
       ```suggestion
   public class StreamTaskSystemExitTest extend TestLogger {
   ```

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+	public void checkExit(int status) {

Review comment:
       ```suggestion
     @Override
   	public void checkExit(int status) {
   ```

##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkUserSecurityManagerTest.java
##########
@@ -0,0 +1,187 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests for {@code FlinkUserSecurityManager}.
+ */
+public class FlinkUserSecurityManagerTest {

Review comment:
       ```suggestion
   public class FlinkUserSecurityManagerTest extend TestLogger {
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     }, {
       "hash" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157",
       "triggerID" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "triggerType" : "PUSH"
     }, {
       "hash" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12186",
       "triggerID" : "359060d80001a5406849d8c7f4b8c58344238ed1",
       "triggerType" : "PUSH"
     }, {
       "hash" : "6bd776d5be680b296e2ab125482fa6bbb63f3c6b",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12189",
       "triggerID" : "6bd776d5be680b296e2ab125482fa6bbb63f3c6b",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 7c7133b540da13a268fddabd97cc873509210f7a Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157) 
   * 359060d80001a5406849d8c7f4b8c58344238ed1 Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12186) 
   * 6bd776d5be680b296e2ab125482fa6bbb63f3c6b Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12189) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559580104



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, true);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);

Review comment:
       Thanks for reminding me about this issue. 
   On my first pass over the code, I didn't understand that the last statement in the test gets never executed due to the exception.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r557501790



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -88,21 +93,28 @@
                                     .build());
 
     @Documentation.Section(Documentation.Sections.EXPERT_CLUSTER)
-    public static final ConfigOption<Boolean> HALT_ON_FATAL_ERROR =
-            key("cluster.processes.halt-on-fatal-error")
+    public static final ConfigOption<Boolean> HALT_ON_SYSTEM_EXIT =
+            key("cluster.processes.halt-on-system-exit")

Review comment:
       The reason why I proposed to change the configuration key away from "halt-on-fatal-error" is that the name is quite misleading. It sounds like fatal errors are causing a System.getRuntime().halt(), however, it means "all system exit calls are replaced by halt()".
   The behavior in case of a fatal error is described in FatalExitExceptionHandler (I guess).
   
   You are right that changing a configuration variable is not advised.  It seems that there was some discussion on the configuration parameter, when it was introduced (last comments: https://issues.apache.org/jira/browse/FLINK-16510). Let's go with @hwanju's proposal and keep the configuration name as-is.
   
   




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-760989075


   Code looks good to me. I'll do one final manual test, and then merge the PR.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r550362614



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+	public void checkExit(int status) {

Review comment:
       Right. Originally forwarding behavior to existing one is via checkPermission override, which is called by `super.checkExit`, but existing manager may do work in checkExit not checkPermission. I will change it.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-758449274


   I'll take a closer look at the chance later today.


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r551763905



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,168 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * {@code FlinkUserSecurityManager} to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private final ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private final CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated based on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+  @Override
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+  @Override
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+  @Override
+	public void checkExit(int status) {
+		super.checkExit(status);
+		if (!systemExitMonitored()) {
+			return;
+		}
+		switch (checkExitMode) {
+			case DISABLED:
+				break;
+			case WARN:
+				// Add exception trace log to help users to debug where exit came from.
+				LOG.warn("Exiting JVM with status {} is monitored, logging and exiting",
+					status, new UserSystemExitException());
+				break;
+			case THROW:
+				throw new UserSystemExitException();
+			default:
+				// Must not happen if exhaustively handling all modes above. Logging as being already at exit path.
+				LOG.warn("No valid check exit mode configured: {}", checkExitMode);
+		}
+	}
+
+	public SecurityManager getOriginalSecurityManager() {
+		return originalSecurityManager;
+	}
+
+	public void monitorSystemExit() {
+		monitorSystemExit.set(true);
+	}
+
+	public void unmonitorSystemExit() {
+		monitorSystemExit.set(false);
+	}
+
+	public boolean systemExitMonitored() {
+		return Boolean.TRUE.equals(monitorSystemExit.get());
+	}
+
+	public static void monitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();
+		if (securityManager instanceof  FlinkUserSecurityManager) {

Review comment:
       ```suggestion
   		if (securityManager instanceof FlinkUserSecurityManager) {
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r551824883



##########
File path: flink-streaming-java/src/main/java/org/apache/flink/streaming/runtime/tasks/StreamTask.java
##########
@@ -705,9 +715,11 @@ public final void cancel() throws Exception {
 
         // the "cancel task" call must come first, but the cancelables must be
         // closed no matter what
+        FlinkUserSecurityManager.monitorSystemExitForCurrentThread();
         try {

Review comment:
       ```suggestion
           FlinkUserSecurityManager.monitorSystemExitForCurrentThread();
           // the "cancel task" call must come first, but the cancelables must be
           // closed no matter what
           try {
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "FAILURE",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "PENDING",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 03e163b7c14a96058f14dd003cbffe8844a78f85 Azure: [FAILURE](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512) 
   * b3f5f5084ab6af669a4f79c47977ad16bab50650 Azure: [PENDING](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-754772366


   > Some thoughts:
   > 
   >     * A SecurityManager should never overwrite an existing one but only intercept the calls it wants to alter. Everything else should be forwarded to the existing SecurityManager, if any.
   
   Right. Originally its forwarding is done via checkPermission, which is lower level call. checkExit was missing and that was added as a later revision. But we seem to think to unify the security managers.
   
   > 
   >     * I think what Robert proposed makes sense: unify the configuration for both SecurityManagers which alter the exit behavior. I'm not sure unifying the code paths makes sense because the logic is quite different although both SecurityManagers alter the exit behavior.
   
   Let me give it a try for how unification looks like.
   
   > 
   >     * This currently only works for StreamTasks? What about batch tasks? We should implement this in a way that it works for all user code.
   
   As discussed in the Jira issue, we start with partial coverage, after which more coverage can be done incrementally with separate tickets. If I move monitoring into Task.doRun, which I will do, we can effectively monitor more coverage not just stream task.
   


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r558333937



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/ClusterOptions.java
##########
@@ -115,4 +127,35 @@ public static boolean isDeclarativeResourceManagementEnabled(Configuration confi
         return configuration.get(ENABLE_DECLARATIVE_RESOURCE_MANAGEMENT)
                 && !System.getProperties().containsKey("flink.tests.disable-declarative");
     }
+
+    /** The mode of how to handle user code attempting to exit JVM. */
+    public enum UserSystemExitMode {
+        DISABLED("Flink is not monitoring or intercepting calls to System.exit()"),
+        LOG("Log exit attempt with stack trace but still allowing exit to be performed"),
+        THROW("Throw exception when exit is attempted disallowing JVM termination");
+
+        private final String description;
+
+        UserSystemExitMode(String description) {
+            this.description = description;
+        }
+
+        public static Description getConfigDescription() {
+            Description.DescriptionBuilder builder = Description.builder();
+            List<TextElement> modeDescriptions =
+                    new ArrayList<>(UserSystemExitMode.values().length);
+            builder.text(
+                    "Flag to check user code exiting system by terminating JVM (e.g., System.exit())");
+            for (UserSystemExitMode mode : UserSystemExitMode.values()) {
+                modeDescriptions.add(
+                        text(String.format("%s - %s", mode.name(), mode.getDescription())));
+            }
+            builder.list(modeDescriptions.toArray(new TextElement[modeDescriptions.size()]));

Review comment:
       ```suggestion
               builder.list(modeDescriptions.toArray(new TextElement[modeDescriptions.size()]));
               builder.text(
                       "This configuration value might change the behavior of %s, when configured to %s and System.exit() gets called in user code.",
                       code(HALT_ON_FATAL_ERROR.key()), code(THROW.name()));
   ```




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     }, {
       "hash" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12079",
       "triggerID" : "56c94ffff67b56b6e6f490103a6be681921d33da",
       "triggerType" : "PUSH"
     }, {
       "hash" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157",
       "triggerID" : "7c7133b540da13a268fddabd97cc873509210f7a",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 7c7133b540da13a268fddabd97cc873509210f7a Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=12157) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-762269311


   I verified that System.exit() in the sources are now properly intercepted! 
   
   From my point of view, this PR is ready to merge. I would wait for CI to pass, and then hit the button. Any last concerns?


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] rmetzger commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
rmetzger commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r551774866



##########
File path: flink-core/src/main/java/org/apache/flink/configuration/SecurityOptions.java
##########
@@ -490,4 +490,25 @@
                                     + "channel. If the `close_notify` was not flushed in the given timeout the channel will be closed "
                                     + "forcibly. (-1 = use system default)")
                     .withDeprecatedKeys("security.ssl.close-notify-flush-timeout");
+
+    // ------------------------ security manager --------------------------------
+
+    /** Flag to check the attempt of main or user-defined function to terminate JVM. */
+    @Documentation.Section(Documentation.Sections.EXPERT_DEBUGGING_AND_TUNING)
+    public static final ConfigOption<String> CHECK_SYSTEM_EXIT =
+            key("security.check-system-exit")

Review comment:
       As commented already, I suggest to move this to the `cluster.` config namespace. I don't see this as a security setting, rather controlling the behavior of the process.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "UNKNOWN",
       "url" : "TBD",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * 84a20ae008df61a397e91e2238bb52afcf3e5124 Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354) 
   * 3ea63637fe7a8c46e434fe3661fe9df444e389a3 UNKNOWN
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r555766406



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;

Review comment:
       I've put it to member as it's almost certainly used for every test for convenience. (Actually it's a bit historical reason that I initialized it to THROW as most tests (before 2nd revision) are with THROW but now it's not initialized). Do you prefer local to member in this case?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r558416279



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, true);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);

Review comment:
       @rmetzger could you adapt this test and the test below to cover the final assert in both cases?




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] flinkbot edited a comment on pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
flinkbot edited a comment on pull request #14499:
URL: https://github.com/apache/flink/pull/14499#issuecomment-751477248


   <!--
   Meta data
   {
     "version" : 1,
     "metaDataEntries" : [ {
       "hash" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11354",
       "triggerID" : "84a20ae008df61a397e91e2238bb52afcf3e5124",
       "triggerType" : "PUSH"
     }, {
       "hash" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11405",
       "triggerID" : "3ea63637fe7a8c46e434fe3661fe9df444e389a3",
       "triggerType" : "PUSH"
     }, {
       "hash" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "status" : "DELETED",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11512",
       "triggerID" : "03e163b7c14a96058f14dd003cbffe8844a78f85",
       "triggerType" : "PUSH"
     }, {
       "hash" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "status" : "SUCCESS",
       "url" : "https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893",
       "triggerID" : "b3f5f5084ab6af669a4f79c47977ad16bab50650",
       "triggerType" : "PUSH"
     } ]
   }-->
   ## CI report:
   
   * b3f5f5084ab6af669a4f79c47977ad16bab50650 Azure: [SUCCESS](https://dev.azure.com/apache-flink/98463496-1af2-4620-8eab-a2ecc1a2e6fe/_build/results?buildId=11893) 
   
   <details>
   <summary>Bot commands</summary>
     The @flinkbot bot supports the following commands:
   
    - `@flinkbot run travis` re-run the last Travis build
    - `@flinkbot run azure` re-run the last Azure build
   </details>


----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] XComp commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
XComp commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r550180192



##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+	public void checkExit(int status) {
+		super.checkExit(status);
+		if (!systemExitMonitored()) {
+			return;
+		}
+		switch (checkExitMode) {
+			case DISABLED:
+				break;
+			case WARN:
+				// Add exception trace log to help users to debug where exit came from.
+				LOG.warn("Exiting JVM with status {} is monitored, logging and exiting",
+					status, new UserSystemExitException());
+				break;
+			case THROW:
+				throw new UserSystemExitException();
+			default:
+				// Must not happen if exhaustively handling all modes above. Logging as being already at exit path.
+				LOG.warn("No valid check exit mode configured: {}", checkExitMode);
+		}
+	}
+
+	public SecurityManager getOriginalSecurityManager() {
+		return originalSecurityManager;
+	}
+
+	public void monitorSystemExit() {
+		monitorSystemExit.set(true);
+	}
+
+	public void unmonitorSystemExit() {
+		monitorSystemExit.set(false);
+	}
+
+	public boolean systemExitMonitored() {
+		return Boolean.TRUE.equals(monitorSystemExit.get());
+	}
+
+	public static void monitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();
+		if (securityManager instanceof  FlinkUserSecurityManager) {
+			((FlinkUserSecurityManager) securityManager).monitorSystemExit();
+		}
+	}
+
+	public static void unmonitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();

Review comment:
       Here we have to be careful considering that multiple `SecurityManagers` can be set wrapped up within each other. Theenabling/disabling might not work depending on the `SecurityManager` initialization order.

##########
File path: flink-runtime/src/main/java/org/apache/flink/runtime/security/FlinkUserSecurityManager.java
##########
@@ -0,0 +1,165 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.configuration.SecurityOptions;
+import org.apache.flink.runtime.UserSystemExitException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Permission;
+
+/**
+ * Flink user security manager to control unexpected user behaviors that potentially impact cluster availability, for
+ * example, it can warn or prevent user code from terminating JVM by System.exit or halt by logging or throwing an exception.
+ * This does not necessarily prevent malicious users who try to tweak security manager on their own, but more for being dependable
+ * against user mistakes by gracefully handling them informing users rather than causing silent unavailability.
+ */
+public class FlinkUserSecurityManager extends SecurityManager {
+
+	/**
+	 * The mode of how to handle user code attempting to exit JVM.
+	 */
+	public enum CheckExitMode {
+		/** No check is enabled, that is allowing exit without any action. */
+		DISABLED,
+		/** Warn by logging but still allowing exit to be performed. */
+		WARN,
+		/** Throw exception when exit is attempted disallowing JVM termination. */
+		THROW,
+	}
+
+	static final Logger LOG = LoggerFactory.getLogger(FlinkUserSecurityManager.class);
+
+	private final SecurityManager originalSecurityManager = System.getSecurityManager();
+	private ThreadLocal<Boolean> monitorSystemExit = new InheritableThreadLocal<>();
+	private CheckExitMode checkExitMode;
+
+	public FlinkUserSecurityManager(CheckExitMode checkExitMode) {
+		super();
+		this.checkExitMode = checkExitMode;
+
+		LOG.info("FlinkUserSecurityManager is created with {} system exit check (previous security manager is {})",
+			this.checkExitMode, originalSecurityManager != null ? originalSecurityManager : "not existing");
+	}
+
+	/**
+	 * Instantiate FlinkUserSecurityManager from configuration. Return null if no security manager
+	 * check is needed, so that a caller can skip setting security manager avoiding runtime check cost,
+	 * if there is no security check set up already. Use {@link #setFromConfiguration} helper, which
+	 * handles disabled case.
+	 *
+	 * @param configuration to instantiate the security manager from
+	 *
+	 * @return FlinkUserSecurityManager instantiated baesd on configuration. Return null if disabled.
+	 */
+	public static FlinkUserSecurityManager fromConfiguration(Configuration configuration) {
+		final String checkExitModeConfig = configuration.getString(SecurityOptions.CHECK_SYSTEM_EXIT);
+		final CheckExitMode checkExitMode;
+
+		try {
+			checkExitMode = CheckExitMode.valueOf(checkExitModeConfig.toUpperCase());
+		} catch (Exception ex) {
+			throw new IllegalConfigurationException(
+				String.format("%s is invalid configuration for %s.", checkExitModeConfig, SecurityOptions.CHECK_SYSTEM_EXIT.key()),
+				ex);
+		}
+
+		// If no check is enabled, return null so that caller can avoid setting security manager not to incur any runtime cost.
+		if (checkExitMode == CheckExitMode.DISABLED) {
+			return null;
+		}
+		// Add more configuration parameters that need user security manager (currently only for system exit).
+		return new FlinkUserSecurityManager(checkExitMode);
+	}
+
+	public static void setFromConfiguration(Configuration configuration) {
+		final FlinkUserSecurityManager flinkUserSecurityManager =
+			FlinkUserSecurityManager.fromConfiguration(configuration);
+		if (flinkUserSecurityManager != null) {
+			System.setSecurityManager(flinkUserSecurityManager);
+		}
+	}
+
+	public void checkPermission(Permission perm) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm);
+		}
+	}
+
+	public void checkPermission(Permission perm, Object context) {
+		if (originalSecurityManager != null) {
+			originalSecurityManager.checkPermission(perm, context);
+		}
+	}
+
+	public void checkExit(int status) {
+		super.checkExit(status);
+		if (!systemExitMonitored()) {
+			return;
+		}
+		switch (checkExitMode) {
+			case DISABLED:
+				break;
+			case WARN:
+				// Add exception trace log to help users to debug where exit came from.
+				LOG.warn("Exiting JVM with status {} is monitored, logging and exiting",
+					status, new UserSystemExitException());
+				break;
+			case THROW:
+				throw new UserSystemExitException();
+			default:
+				// Must not happen if exhaustively handling all modes above. Logging as being already at exit path.
+				LOG.warn("No valid check exit mode configured: {}", checkExitMode);
+		}
+	}
+
+	public SecurityManager getOriginalSecurityManager() {
+		return originalSecurityManager;
+	}
+
+	public void monitorSystemExit() {
+		monitorSystemExit.set(true);
+	}
+
+	public void unmonitorSystemExit() {
+		monitorSystemExit.set(false);
+	}
+
+	public boolean systemExitMonitored() {
+		return Boolean.TRUE.equals(monitorSystemExit.get());
+	}
+
+	public static void monitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();
+		if (securityManager instanceof  FlinkUserSecurityManager) {
+			((FlinkUserSecurityManager) securityManager).monitorSystemExit();
+		}
+	}
+
+	public static void unmonitorSystemExitForCurrentThread() {
+		SecurityManager securityManager = System.getSecurityManager();

Review comment:
       Here we have to be careful considering that multiple `SecurityManagers` can be set wrapped up within each other. The enabling/disabling might not work depending on the `SecurityManager` initialization order.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



[GitHub] [flink] hwanju commented on a change in pull request #14499: [FLINK-15156] Warn user if System.exit() is called in user code

Posted by GitBox <gi...@apache.org>.
hwanju commented on a change in pull request #14499:
URL: https://github.com/apache/flink/pull/14499#discussion_r559583663



##########
File path: flink-runtime/src/test/java/org/apache/flink/runtime/security/FlinkSecurityManagerTest.java
##########
@@ -0,0 +1,415 @@
+/*
+ * 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.flink.runtime.security;
+
+import org.apache.flink.configuration.ClusterOptions;
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.IllegalConfigurationException;
+import org.apache.flink.core.testutils.CheckedThread;
+import org.apache.flink.runtime.UserSystemExitException;
+import org.apache.flink.util.TestLogger;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.security.Permission;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.flink.core.testutils.CommonTestUtils.assertThrows;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/** Tests for {@code FlinkUserSecurityManager}. */
+public class FlinkSecurityManagerTest extends TestLogger {
+
+    private static final int TEST_EXIT_CODE = 123;
+    SecurityManager originalSecurityManager;
+    FlinkSecurityManager flinkSecurityManager;
+
+    @Before
+    public void setUp() {
+        originalSecurityManager = System.getSecurityManager();
+    }
+
+    @After
+    public void tearDown() {
+        System.setSecurityManager(originalSecurityManager);
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testThrowUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testToggleUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testPerThreadThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        ExecutorService executorService = Executors.newSingleThreadExecutor();
+        // Async thread test before enabling monitoring ensures it does not throw while prestarting
+        // worker thread, which is to be unmonitored and tested after enabling monitoring enabled.
+        CompletableFuture<Void> future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        // This threaded exit should be allowed as thread is not spawned while monitor is enabled.
+        future =
+                CompletableFuture.runAsync(
+                        () -> flinkSecurityManager.checkExit(TEST_EXIT_CODE), executorService);
+        future.get();
+    }
+
+    @Test
+    public void testInheritedThrowUserExit() throws Exception {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        CheckedThread thread =
+                new CheckedThread() {
+                    @Override
+                    public void go() {
+                        try {
+                            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                            fail();
+                        } catch (UserSystemExitException ignored) {
+                        } catch (Throwable t) {
+                            fail();
+                        }
+                    }
+                };
+        thread.start();
+        thread.sync();
+    }
+
+    @Test
+    public void testLogUserExit() {
+        // Log mode enables monitor but only logging allowing exit, hence not expecting exception.
+        // NOTE - Do not specifically test warning logging.
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.LOG, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testExitBehaviorChanged() {
+        AtomicInteger exitStatus = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> exitStatus.set(status));
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(exitStatus.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test
+    public void testExitBehaviorChangedWithExistingSecurityManager() {
+        TestExitSecurityManager existingSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(existingSecurityManager);
+        AtomicInteger customExitExecuted = new AtomicInteger(0);
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED, customExitExecuted::set);
+
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        assertThat(existingSecurityManager.getExitStatus(), is(TEST_EXIT_CODE));
+        assertThat(customExitExecuted.get(), is(TEST_EXIT_CODE));
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testExitBehaviorUnchangeOnThrowingUserExit() {
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.THROW, status -> fail());
+        flinkSecurityManager.monitorUserSystemExit();
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testDisabledConfiguration() {
+        // Default case (no provided option) - allowing everything, so null security manager is
+        // expected.
+        Configuration configuration = new Configuration();
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // Disabled case (same as default)
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT,
+                ClusterOptions.UserSystemExitMode.DISABLED);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+
+        // No halt (same as default)
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNull(flinkSecurityManager);
+    }
+
+    @Test
+    public void testLogConfiguration() {
+        // Enabled - log case (logging as warning but allowing exit)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.LOG);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+    }
+
+    @Test
+    public void testThrowConfiguration() {
+        // Enabled - throw case (disallowing by throwing exception)
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        try {
+            flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+            fail();
+        } catch (UserSystemExitException ignored) {
+        }
+        flinkSecurityManager.unmonitorUserSystemExit();
+        assertFalse(flinkSecurityManager.userSystemExitMonitored());
+
+        // Test for disabled test to check if exit is still allowed (fromConfiguration gives null
+        // since currently
+        // there is only one option to have a valid security manager, so test with constructor).
+        flinkSecurityManager =
+                new FlinkSecurityManager(ClusterOptions.UserSystemExitMode.DISABLED, null);
+        flinkSecurityManager.monitorUserSystemExit();
+        assertTrue(flinkSecurityManager.userSystemExitMonitored());
+        flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+    }
+
+    @Test
+    public void testHaltConfiguration() {
+        // Halt as forceful shutdown replacing graceful system exit
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, true);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+        assertNotNull(flinkSecurityManager);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testInvalidConfiguration() {
+        Configuration configuration = new Configuration();
+        configuration.set(ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, null);
+        flinkSecurityManager = FlinkSecurityManager.fromConfiguration(configuration);
+    }
+
+    @Test
+    public void testExistingSecurityManagerRespected() {
+        // Don't set the following security manager directly to system, which makes test hang.
+        SecurityManager originalSecurityManager =
+                new SecurityManager() {
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        throw new SecurityException("not allowed");
+                    }
+                };
+        flinkSecurityManager =
+                new FlinkSecurityManager(
+                        ClusterOptions.UserSystemExitMode.DISABLED,
+                        status -> Assert.fail(),
+                        originalSecurityManager);
+
+        assertThrows(
+                "not allowed",
+                SecurityException.class,
+                () -> {
+                    flinkSecurityManager.checkExit(TEST_EXIT_CODE);
+                    return null;
+                });
+    }
+
+    @Test
+    public void testRegistrationNotAllowedByExistingSecurityManager() {
+        Configuration configuration = new Configuration();
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+
+        System.setSecurityManager(
+                new SecurityManager() {
+
+                    private boolean fired;
+
+                    @Override
+                    public void checkPermission(Permission perm) {
+                        if (!fired && perm.getName().equals("setSecurityManager")) {
+                            try {
+                                throw new SecurityException("not allowed");
+                            } finally {
+                                // Allow removing this manager again
+                                fired = true;
+                            }
+                        }
+                    }
+                });
+
+        assertThrows(
+                "Could not register security manager",
+                IllegalConfigurationException.class,
+                () -> {
+                    FlinkSecurityManager.setFromConfiguration(configuration);
+                    return null;
+                });
+    }
+
+    @Test(expected = UserSystemExitException.class)
+    public void testMultiSecurityManagersWithSetFirstAndMonitored() {
+        Configuration configuration = new Configuration();
+
+        configuration.set(
+                ClusterOptions.INTERCEPT_USER_SYSTEM_EXIT, ClusterOptions.UserSystemExitMode.THROW);
+        configuration.set(ClusterOptions.HALT_ON_SYSTEM_EXIT, false);
+
+        FlinkSecurityManager.setFromConfiguration(configuration);
+
+        TestExitSecurityManager newSecurityManager = new TestExitSecurityManager();
+        System.setSecurityManager(newSecurityManager);
+
+        FlinkSecurityManager.monitorUserSystemExitForCurrentThread();
+        newSecurityManager.checkExit(TEST_EXIT_CODE);

Review comment:
       Sorry that I missed this comment. Thought I went over all but missing this. Looks like Robert is doing changes, thanks!




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
users@infra.apache.org