Index: paramiko-2.4.0/paramiko/pkey.py
===================================================================
--- paramiko-2.4.0.orig/paramiko/pkey.py
+++ paramiko-2.4.0/paramiko/pkey.py
@@ -351,7 +351,18 @@ class PKey(object):
 
         :raises: ``IOError`` -- if there was an error writing the file.
         """
-        with open(filename, 'w') as f:
+        # Ensure that we create new key files directly with a user-only mode,
+        # instead of opening, writing, then chmodding, which leaves us open to
+        # CVE-2022-24302.
+        # NOTE: O_TRUNC is a noop on new files, and O_CREAT is a noop on
+        # existing files, so using all 3 in both cases is fine. Ditto the use
+        # of the 'mode' argument; it should be safe to give even for existing
+        # files (though it will not act like a chmod in that case).
+        # TODO 3.0: turn into kwargs again
+        args = [os.O_WRONLY | os.O_TRUNC | os.O_CREAT, o600]
+        # NOTE: yea, you still gotta inform the FLO that it is in "write" mode
+        with os.fdopen(os.open(filename, *args), "w") as f:
+            # TODO 3.0: remove the now redundant chmod
             os.chmod(filename, o600)
             self._write_private_key(f, key, format, password=password)
 
Index: paramiko-2.4.0/tests/test_pkey.py
===================================================================
--- paramiko-2.4.0.orig/tests/test_pkey.py
+++ paramiko-2.4.0/tests/test_pkey.py
@@ -23,12 +23,16 @@ Some unit tests for public/private key o
 
 import unittest
 import os
+import stat
 from binascii import hexlify
 from hashlib import md5
 import base64
 
 from paramiko import RSAKey, DSSKey, ECDSAKey, Ed25519Key, Message, util
 from paramiko.py3compat import StringIO, byte_chr, b, bytes, PY2
+from paramiko.common import o600
+
+from mock import patch, Mock
 
 from .util import _support
 
@@ -522,3 +526,57 @@ class KeyTest(unittest.TestCase):
             key1.load_certificate,
             _support('test_rsa.key-cert.pub'),
         )
+
+    @patch("paramiko.pkey.os")
+    def _test_keyfile_race(self, os_, exists):
+        # Re: CVE-2022-24302
+        password = "television"
+        newpassword = "radio"
+        source = _support("test_ecdsa_384.key")
+        new = source + ".new"
+        # Mock setup
+        os_.path.exists.return_value = exists
+        # Attach os flag values to mock
+        for attr, value in vars(os).items():
+            if attr.startswith("O_"):
+                setattr(os_, attr, value)
+        # Load fixture key
+        key = ECDSAKey(filename=source, password=password)
+        key._write_private_key = Mock()
+        # Write out in new location
+        key.write_private_key_file(new, password=newpassword)
+        # Expected open via os module
+        os_.open.assert_called_once_with(new, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, o600)
+        os_.fdopen.assert_called_once_with(os_.open.return_value, "w")
+        # Old chmod still around for backwards compat
+        os_.chmod.assert_called_once_with(new, o600)
+        assert (
+            key._write_private_key.call_args[0][0]
+            == os_.fdopen.return_value.__enter__.return_value
+        )
+
+    def test_new_keyfiles_avoid_file_descriptor_race_on_chmod(self):
+        self._test_keyfile_race(exists=False)
+
+    def test_existing_keyfiles_still_work_ok(self):
+        self._test_keyfile_race(exists=True)
+
+    def test_new_keyfiles_avoid_descriptor_race_integration(self):
+        # Integration-style version of above
+        password = "television"
+        newpassword = "radio"
+        source = _support("test_ecdsa_384.key")
+        new = source + ".new"
+        # Load fixture key
+        key = ECDSAKey(filename=source, password=password)
+        try:
+            # Write out in new location
+            key.write_private_key_file(new, password=newpassword)
+            # Test mode
+            assert stat.S_IMODE(os.stat(new).st_mode) == o600
+            # Prove can open with new password
+            reloaded = ECDSAKey(filename=new, password=newpassword)
+            assert reloaded == key
+        finally:
+            if os.path.exists(new):
+                os.unlink(new)
