@@ -89,6 +89,25 @@ var EVT_NEW_VOICE_MESSAGE = 'newVoiceMessage';
8989 */
9090var AUDIO_RECORDED_PATH = '/var/spool/asterisk/tmp' ;
9191
92+ /**
93+ * Resolves a name to an absolute path that is guaranteed to stay inside
94+ * AUDIO_RECORDED_PATH. Throws if the resulting path would be the directory
95+ * itself or escape it, so every filesystem operation is forced under the
96+ * recordings directory regardless of the name components.
97+ *
98+ * @method resolveRecordedPath
99+ * @param {string } name The filename to place inside AUDIO_RECORDED_PATH
100+ * @return {string } The absolute path inside AUDIO_RECORDED_PATH
101+ * @private
102+ */
103+ function resolveRecordedPath ( name ) {
104+ var resolved = path . resolve ( AUDIO_RECORDED_PATH , String ( name ) ) ;
105+ if ( resolved . indexOf ( AUDIO_RECORDED_PATH + path . sep ) !== 0 ) {
106+ throw new Error ( 'refusing to use a path outside the recordings directory' ) ;
107+ }
108+ return resolved ;
109+ }
110+
92111/**
93112 * The mpg123 binary for MP3 conversion.
94113 *
@@ -836,8 +855,8 @@ function setCustomVmAudioMsg(vm, type, audio, cb) {
836855 throw new Error ( 'uploaded audio is not a valid RIFF WAV file (missing RIFF header)' ) ;
837856 }
838857 var tmpFilename = 'vm_' + vm + '_' + type + '_' + Date . now ( ) + '_' + process . pid + '.wav' ;
839- var tmpPath = path . join ( AUDIO_RECORDED_PATH , tmpFilename ) ;
840- var tmpOutPath = path . join ( AUDIO_RECORDED_PATH , 'vm_' + vm + '_' + type + '_' + Date . now ( ) + '_' + process . pid + '_out.wav' ) ;
858+ var tmpPath = resolveRecordedPath ( tmpFilename ) ;
859+ var tmpOutPath = resolveRecordedPath ( 'vm_' + vm + '_' + type + '_' + Date . now ( ) + '_' + process . pid + '_out.wav' ) ;
841860 async . waterfall ( [
842861 function ( callback ) {
843862 var tmpExt = ( normalized . detectedType === 'mp3' ? '.mp3' : '.wav' ) ;
@@ -916,9 +935,19 @@ function setCustomVmAudioMsgFromFile(vm, type, tempFilename, cb) {
916935 throw new Error ( 'wrong parameters: ' + JSON . stringify ( arguments ) ) ;
917936 }
918937
919- var sourcePath = path . join ( AUDIO_RECORDED_PATH , tempFilename ) ;
938+ // tempFilename must be a bare filename inside AUDIO_RECORDED_PATH: reject any
939+ // name with path components and the "", ".", ".." special names so that
940+ // readFile/stat/unlink always operate inside the recordings directory.
941+ var safeTempFilename = path . basename ( tempFilename ) ;
942+ if ( safeTempFilename !== tempFilename ||
943+ safeTempFilename === '' || safeTempFilename === '.' || safeTempFilename === '..' ) {
944+
945+ throw new Error ( 'invalid tempFilename' ) ;
946+ }
947+
948+ var sourcePath = resolveRecordedPath ( safeTempFilename ) ;
920949 var convertedTmpPath = null ;
921- var ext = path . extname ( tempFilename ) ;
950+ var ext = path . extname ( safeTempFilename ) ;
922951 var extLower = ext . toLowerCase ( ) ;
923952
924953 // sequentially executes operations:
@@ -952,7 +981,7 @@ function setCustomVmAudioMsgFromFile(vm, type, tempFilename, cb) {
952981 return ;
953982 }
954983 }
955- var tmpOutPath = path . join ( AUDIO_RECORDED_PATH , 'vm_' + vm + '_' + type + '_' + Date . now ( ) + '_' + process . pid + '_out.wav' ) ;
984+ var tmpOutPath = resolveRecordedPath ( 'vm_' + vm + '_' + type + '_' + Date . now ( ) + '_' + process . pid + '_out.wav' ) ;
956985 convertedTmpPath = tmpOutPath ;
957986 fs . stat ( sourcePath , function ( err ) {
958987 if ( err ) {
0 commit comments