From 0a8c34ea152311d83fb815db16ddecb4806ac9fd Mon Sep 17 00:00:00 2001 From: Anthony Zhang Date: Wed, 22 Feb 2017 02:23:30 -0500 Subject: [PATCH 1/2] bpo-29110: Fix file object leak in `aifc.open` when given invalid AIFF file. (GH-162) (cherry picked from commit 03f68b60e17b57f6f13729ff73245dbb37b30a4c) --- Lib/aifc.py | 33 +++++++++++++++++++++------------ Lib/test/test_aifc.py | 10 +++++++++- Misc/NEWS | 3 +++ 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Lib/aifc.py b/Lib/aifc.py index 692d0bfd272bf0..380adc8d0da619 100644 --- a/Lib/aifc.py +++ b/Lib/aifc.py @@ -344,9 +344,15 @@ def initfp(self, file): def __init__(self, f): if isinstance(f, str): - f = builtins.open(f, 'rb') - # else, assume it is an open file object already - self.initfp(f) + file_object = builtins.open(f, 'rb') + try: + self.initfp(file_object) + except: + file_object.close() + raise + else: + # assume it is an open file object already + self.initfp(f) def __enter__(self): return self @@ -543,16 +549,19 @@ class Aifc_write: def __init__(self, f): if isinstance(f, str): - filename = f - f = builtins.open(f, 'wb') - else: - # else, assume it is an open file object already - filename = '???' - self.initfp(f) - if filename[-5:] == '.aiff': - self._aifc = 0 + file_object = builtins.open(f, 'wb') + try: + self.initfp(file_object) + except: + file_object.close() + raise + + # treat .aiff file extensions as non-compressed audio + if f.endswith('.aiff'): + self._aifc = 0 else: - self._aifc = 1 + # assume it is an open file object already + self.initfp(f) def initfp(self, file): self._file = file diff --git a/Lib/test/test_aifc.py b/Lib/test/test_aifc.py index 1bd1f89c8aa609..989df93a3a5741 100644 --- a/Lib/test/test_aifc.py +++ b/Lib/test/test_aifc.py @@ -1,4 +1,4 @@ -from test.support import findfile, TESTFN, unlink +from test.support import check_no_resource_warning, findfile, TESTFN, unlink import unittest from test import audiotests from audioop import byteswap @@ -149,6 +149,14 @@ def test_skipunknown(self): #This file contains chunk types aifc doesn't recognize. self.f = aifc.open(findfile('Sine-1000Hz-300ms.aif')) + def test_close_opened_files_on_error(self): + non_aifc_file = findfile('pluck-pcm8.wav', subdir='audiodata') + with check_no_resource_warning(self): + with self.assertRaises(aifc.Error): + # Try opening a non-AIFC file, with the expectation that + # `aifc.open` will fail (without raising a ResourceWarning) + f = self.f = aifc.open(non_aifc_file, 'rb') + def test_params_added(self): f = self.f = aifc.open(TESTFN, 'wb') f.aiff() diff --git a/Misc/NEWS b/Misc/NEWS index 0f5c27902ba423..6b446b249af2f2 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -72,6 +72,9 @@ Library - bpo-29532: Altering a kwarg dictionary passed to functools.partial() no longer affects a partial object after creation. +- bpo-29110: Fix file object leak in aifc.open() when file is given as a + filesystem path and is not in valid AIFF format. Patch by Anthony Zhang. + - bpo-22807: Add uuid.SafeUUID and uuid.UUID.is_safe to relay information from the platform about whether generated UUIDs are generated with a multiprocessing safe method. From 09d29fcfc5b58ffc32032efd48c092723a2f2278 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 26 Feb 2017 21:11:58 +0900 Subject: [PATCH 2/2] bpo-29110: add test for Aifc_write. (GH-293) follow up of GH-162 (cherry picked from commit 5dc33eea538361f8a218255f83db2e9298dd8c53) --- Lib/aifc.py | 4 ++++ Lib/test/test_aifc.py | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Lib/aifc.py b/Lib/aifc.py index 380adc8d0da619..13ad7dc5ca3d62 100644 --- a/Lib/aifc.py +++ b/Lib/aifc.py @@ -303,6 +303,8 @@ class Aifc_read: # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk # _framesize -- size of one frame in the file + _file = None # Set here since __del__ checks it + def initfp(self, file): self._version = 0 self._convert = None @@ -547,6 +549,8 @@ class Aifc_write: # _datalength -- the size of the audio samples written to the header # _datawritten -- the size of the audio samples actually written + _file = None # Set here since __del__ checks it + def __init__(self, f): if isinstance(f, str): file_object = builtins.open(f, 'wb') diff --git a/Lib/test/test_aifc.py b/Lib/test/test_aifc.py index 989df93a3a5741..a731a5136ba5fd 100644 --- a/Lib/test/test_aifc.py +++ b/Lib/test/test_aifc.py @@ -1,5 +1,6 @@ from test.support import check_no_resource_warning, findfile, TESTFN, unlink import unittest +from unittest import mock from test import audiotests from audioop import byteswap import io @@ -155,7 +156,14 @@ def test_close_opened_files_on_error(self): with self.assertRaises(aifc.Error): # Try opening a non-AIFC file, with the expectation that # `aifc.open` will fail (without raising a ResourceWarning) - f = self.f = aifc.open(non_aifc_file, 'rb') + self.f = aifc.open(non_aifc_file, 'rb') + + # Aifc_write.initfp() won't raise in normal case. But some errors + # (e.g. MemoryError, KeyboardInterrupt, etc..) can happen. + with mock.patch.object(aifc.Aifc_write, 'initfp', + side_effect=RuntimeError): + with self.assertRaises(RuntimeError): + self.fout = aifc.open(TESTFN, 'wb') def test_params_added(self): f = self.f = aifc.open(TESTFN, 'wb')