os.urandom、CPython、Linuxおよびrake





UNIXライクなOS(Linux、Mac OS Xなど)上のCPythonのosモジュールからのurandom関数の実装における、有益なエラーストーリーを伝えたいと思います。



トリプルドキュメントからの引用:

暗号化の使用に適したn個のランダムバイトの文字列を返します。



この関数は、OS固有の乱数ソースからランダムバイトを返します。 正確な品質はOSの実装に依存しますが、返されるデータは暗号化アプリケーションに十分なほど予測不能でなければなりません。 Unixライクなシステムでは、これは/ dev / urandomを照会し、WindowsではCryptGenRandom()を使用します。
ドキュメントのドキュメントは次を追加します:

バージョン2.4の新機能。
言い換えると、たとえば、Linuxでは、urandomはシステムデバイス/ dev / urandomからバイトを読み取って返します。 このOSには、エントロピーのための2つの典型的なソースデバイスがあります:/ dev / randomおよび/ dev / urandom。 ご存知のように、最初のデバイスは「低速」かつブロッキングであり、2番目のデバイスは「高速」であり、一般的な考えに反して、 どちらも(擬似)乱数の暗号ソースです 。 KDPVはこの記事とは無関係であり、暗号化、セキュリティ、 またはHeartbleedを使用したOpenSSLに関するものではないことをすぐに言わなければなりません。



このような単純なルーチンの実装で、どのようにミスを犯すことができるのでしょうか? よくあることですが、最適化されています...



2.4


2004年の終わりに戻ると、 Half-Life 2 CPython 2.4がリリースされ、関数デコレータ、セット、セットの逆順、リスト内包表記などの一般的な機能が追加されました。 それらのない人々がPythonでソフトウェアを開発することさえできますか?!



Python自体に実装されているos.urandomの追加を含めて、すでに上記で記述されています。 urandomの書き方を想像してみましょう。

def urandom n

open '/ dev / urandom' 'rb' rnd として

rndを返します。 読み取り n
だから、3行。 さらに、これは、ドックの機能作業の仕様に準拠するために、例外やその他の詳細の処理を除いて、エラーのない絶対に正しい実装です。 そして、誰かの明るい頭は、このコードを高速化することを示唆しています。 これはどのように可能ですか、あなたは尋ねます。 ファイルオブジェクトをキャッシュすると、ライトヘッドが応答します。

rnd = なし



def urandom n

rnd Noneの 場合

rnd = open '/ dev / urandom' 'rb'

rndを返します。 読み取り n
そのような実装ではどのような問題が発生しますか? デーモンになるスクリプトは、親の死後にurandomが最初に呼び出されたときにクラッシュします。



フォーク()


2001 POSIX標準の一部であり、Unixの最初のバージョンに登場したシステム関数fork()は、同じ環境で別のアドレススペースを持つシステムにプロセスツインが現れると、「分岐」メソッドを使用して新しいプロセスを生成するように設計されていることを知っています。そして、fork()呼び出しがあったコードのまさにその場所から正確に動作し始めます。 原則として、フォークはコピーオンライトメカニズムを使用します。これは、ツインプロセス(「子」)の作成時にメモリが物理的にコピーされないためです。 代わりに、双子が作業中に書き込むページは、親のメモリからコピーされます。 これがすべての歌詞ですが、man forkからの次の引用に興味があります。

子は、親の開いているファイル記述子のセットのコピーを継承します。 子の各ファイル記述子は、親の対応するファイル記述子と同じオープンファイル記述(open(2)を参照)を参照します。 これは、2つの記述子がオープンファイルステータスフラグ、現在のファイルオフセット、および信号駆動型I / O属性を共有することを意味します
つまり、フォーク後のPythonファイルオブジェクトに属するファイル記述子は相互接続され、同じファイルを参照します。 ただし、あるプロセスでファイルが閉じられた場合、別のプロセスで自動的に閉じられることはありません



フォークとフォーク、あなたは言います。 Pythonはここにありますか? そして、という事実にもかかわらず

  1. マルチプロセッシング*その上で動作します
  2. 悪魔はそれを通して起こります
*修正番号#8713が 常に使用されるとは限りません



マルチプロセッシングでの分岐のおかげで、子供たちは最初は繁殖前のメインプロセスの状態にあります。 悪魔化プロセス(Windowsの観点からサービスに変わる)について-PEP 3143を参照してください。 フルスイングのどこかにfork()呼び出しがあります。 そして、最良の伝統に従って、close()を介してではなく、新しく作成されたデーモンですべてのファイル記述子を直接閉じた場合(たとえばos。Closerange 3、256 )、os.urandom()がクラッシュします。



2005年の初め頃、CPythonユーザー開発者に間違いを説明しました。 しかし、グイドは最初に自分を愚か者に作り上げて否定しようとしました

これを無効として閉じることをお勧めします。 デーモン化コードは明らかに壊れています。
幸いなことに、人々は反対者の王を納得させることができ、最後に、7月に/ dev / urandomのキャッシュが削除されました-6か月以上が経過しました。 これがどのように行われたかに注目します。コードには、バグ番号へのリンクも、パッチの理由の表示も、最終的には単なる説明コメントもありません。 それは動作し、良いです。



3.4


9年かかります。 2014年3月に CPython 3.4がリリースされました。 彼は次のような必要な機能を追加します。

Python 3.4では、新しい構文機能は追加されていません。
さて、真剣に、進歩は大きいです。たとえば、多くのライブラリが受け入れられました。たとえば、asyncioはすでにHabréでたくさん書かれていて、セキュリティを改善し、オブジェクトのリリースを解放しました-私はこれについて話すのではありません。 主なことは、リリース前に、Pythonでの/ dev / urandomの実装が非常に遅く、古き良きCだけが真のパフォーマンスを提供できると感じていた人がいたことです。一般的に、関数は書き直されました... そして、彼らを助けたPEP 446はありませんでした。 パッチは4月24日にリリースされ 、今回は既に豊富なコメント、バグへのリンク、さらには回帰テストが含まれていました。



私は何を気にしますか


記事のボーナスとして、このエラーにつまずいた方法を説明します。 Ubuntu 14.04 LTSが動作しているシステムがありますが、残念ながらその上にあります

インポート プラットフォーム

プラットフォーム python_build

'default' 'Apr 11 2014 13:05:11'
コードのデモンストレーションが機能し、すべてのファイル記述子を閉じました。 そして、ここに問題があります

輸入 OS

print os。listdir '/ proc / self / fd' ))

ランダムに インポート

print os。listdir '/ proc / self / fd' ))
プリント

['0', '1', '2', '3']





['0', '1', '2', '3', '4']



実験は完全にきれいではありません、なぜなら os.listdirは、どちらの場合も最後の番号の下に記述子を作成します。 ランダムにインポートした後、番号3が開かれましたが、どのファイルに対応していますか?

print os。readlink '/ proc / self / fd / 3'
/dev/urandom



タダム! モジュールをインポートするとき、私はいつも悪い態度を持っていました...この場合、random.pyの終わりを引用します:

OS から _urandom として urandom インポート



クラス Random _random。Random

#...

def __init__ self x = None

#...

自己シード x

自己gauss_next = なし



def seed self a = None version = 2

#...

a Noneの 場合

試してください

a = intfrom_bytes _urandom 32 'big'

NotImplementedError を除く

#...
インポートランダムは、Tornado、Twisted、uuid、およびその他の多くのライブラリによって作成され、標準ではなく、非常に重要であることに注意してください。



最初は問題の本質を正確に理解していなかったため、子と親のファイル記述子が同時に閉じられていると不当に決定したことに注意してください。 このバグの全体像復元してくれてありがとうkekekeks



結論


ライブラリを開発するときは、常にfork()の永遠の問題について考え、常にコードのバグ修正についてコメントし、ユーザーの問題に関するメッセージを注意深く読んでください(少なくともプログラマの場合)。



All Articles