@@ -22,6 +22,13 @@ LOG_MODULE_REGISTER(ftp_client, CONFIG_FTP_CLIENT_LOG_LEVEL);
2222
2323#define FTP_CODE_ANY 0
2424
25+ /* FTP parameter length limits */
26+ #define FTP_MAX_USERNAME 64 /* Unix/Linux username typical limit */
27+ #define FTP_MAX_PASSWORD 255 /* Allow longer passwords */
28+ #define FTP_MAX_FILENAME 255 /* Filename length limit */
29+ #define FTP_MAX_PATHNAME 255 /* Standard NAME_MAX */
30+ #define FTP_MAX_OPTIONS 32 /* FTP LIST options are typically short */
31+
2532#define FTP_STACK_SIZE KB(2)
2633#define FTP_PRIORITY K_LOWEST_APPLICATION_THREAD_PRIO
2734static K_THREAD_STACK_DEFINE (ftp_stack_area , FTP_STACK_SIZE ) ;
@@ -51,14 +58,32 @@ enum data_task_type {
5158static struct data_task {
5259 struct k_work work ;
5360 enum data_task_type task ;
54- char * ctrl_msg ; /* PSAV resposne */
61+ char ctrl_msg [ 128 ]; /* PASV response copy */
5562 uint8_t * data ; /* TX data */
5663 uint16_t length ; /* TX length */
5764} data_task_param ;
5865
5966static bool ftp_inactivity ;
6067static atomic_t data_task_running ;
6168
69+ static int validate_ftp_param (const char * param , size_t max_len )
70+ {
71+ if (param == NULL ) {
72+ return - EINVAL ;
73+ }
74+
75+ if (strlen (param ) > max_len ) {
76+ return - EINVAL ;
77+ }
78+
79+ /* Check for FTP command injection - CR/LF can inject additional commands */
80+ if (strchr (param , '\r' ) || strchr (param , '\n' )) {
81+ return - EINVAL ;
82+ }
83+
84+ return 0 ;
85+ }
86+
6287static int parse_return_code (const uint8_t * message , int success_code )
6388{
6489 if (success_code == FTP_CODE_ANY ) {
@@ -92,7 +117,8 @@ static int parse_return_code(const uint8_t *message, int success_code)
92117static int establish_data_channel (const char * pasv_msg )
93118{
94119 int ret ;
95- char tmp [8 ];
120+ char * endptr ;
121+ long port_byte ;
96122 char * tmp1 , * tmp2 ;
97123 uint16_t data_port ;
98124
@@ -109,19 +135,33 @@ static int establish_data_channel(const char *pasv_msg)
109135 if (tmp2 == NULL ) {
110136 return - EINVAL ;
111137 }
112- memset (tmp , 0x00 , sizeof (tmp ));
113- strncpy (tmp , (const char * )(tmp2 + 1 ), (size_t )(tmp1 - tmp2 - 1 ));
114- data_port = atoi (tmp );
138+ /* Validate that ')' appears after the last ',' */
139+ if (tmp1 <= tmp2 ) {
140+ return - EINVAL ;
141+ }
142+
143+ /* Extract low byte of port (p2) */
144+ port_byte = strtol (tmp2 + 1 , & endptr , 10 );
145+ if (endptr != tmp1 || port_byte < 0 || port_byte > 255 ) {
146+ return - EINVAL ;
147+ }
148+ data_port = (uint16_t )port_byte ;
149+
150+ /* Find previous comma for high byte */
115151 tmp1 = tmp2 - 1 ;
116- while (isdigit ((int )(* tmp1 ))) {
152+ while (tmp1 > pasv_msg && isdigit ((int )(* tmp1 ))) {
117153 tmp1 -- ;
118154 }
119- if (* tmp1 != ',' ) {
155+ if (tmp1 <= pasv_msg || * tmp1 != ',' ) {
120156 return - EINVAL ;
121157 }
122- memset (tmp , 0x00 , sizeof (tmp ));
123- strncpy (tmp , (const char * )(tmp1 + 1 ), (size_t )(tmp2 - tmp1 - 1 ));
124- data_port += atoi (tmp ) << 8 ;
158+
159+ /* Extract high byte of port (p1) */
160+ port_byte = strtol (tmp1 + 1 , & endptr , 10 );
161+ if (endptr != tmp2 || port_byte < 0 || port_byte > 255 ) {
162+ return - EINVAL ;
163+ }
164+ data_port += (uint16_t )(port_byte << 8 );
125165 LOG_DBG ("data port: %d" , data_port );
126166
127167 /* open data socket */
@@ -171,22 +211,25 @@ static void close_connection(int code, int error)
171211 if (FTP_PROPRIETARY (code )) {
172212 switch (code ) {
173213 case FTP_CODE_901 :
174- sprintf (ctrl_buf , "901 Disconnected(%d).\r\n" , error );
214+ snprintf (ctrl_buf , sizeof ( ctrl_buf ) , "901 Disconnected(%d).\r\n" , error );
175215 break ;
176216 case FTP_CODE_902 :
177- sprintf (ctrl_buf , "902 Connection aborted(%d).\r\n" , error );
217+ snprintf (ctrl_buf , sizeof (ctrl_buf ), "902 Connection aborted(%d).\r\n" ,
218+ error );
178219 break ;
179220 case FTP_CODE_903 :
180- sprintf (ctrl_buf , "903 Poll error(%d).\r\n" , error );
221+ snprintf (ctrl_buf , sizeof ( ctrl_buf ) , "903 Poll error(%d).\r\n" , error );
181222 break ;
182223 case FTP_CODE_904 :
183- sprintf (ctrl_buf , "904 Unexpected poll event(%d).\r\n" , error );
224+ snprintf (ctrl_buf , sizeof (ctrl_buf ), "904 Unexpected poll event(%d).\r\n" ,
225+ error );
184226 break ;
185227 case FTP_CODE_905 :
186- sprintf (ctrl_buf , "905 Network down (%d).\r\n" , error );
228+ snprintf (ctrl_buf , sizeof ( ctrl_buf ) , "905 Network down (%d).\r\n" , error );
187229 break ;
188230 default :
189- sprintf (ctrl_buf , "900 Unknown error(%d).\r\n" , - ENOEXEC );
231+ snprintf (ctrl_buf , sizeof (ctrl_buf ), "900 Unknown error(%d).\r\n" ,
232+ - ENOEXEC );
190233 break ;
191234 }
192235 client .ctrl_callback (ctrl_buf , strlen (ctrl_buf ));
@@ -507,7 +550,7 @@ int ftp_open(const char *hostname, uint16_t port, int sec_tag)
507550 }
508551
509552 /* Send UTF8 option */
510- sprintf (ctrl_buf , CMD_OPTS , "UTF8 ON" );
553+ snprintf (ctrl_buf , sizeof ( ctrl_buf ) , CMD_OPTS , "UTF8 ON" );
511554 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
512555 if (ret ) {
513556 zsock_close (client .cmd_sock );
@@ -524,16 +567,26 @@ int ftp_login(const char *username, const char *password)
524567 int ret ;
525568 int keepalive_time = CONFIG_FTP_CLIENT_KEEPALIVE_TIME ;
526569
570+ /* Validate inputs */
571+ ret = validate_ftp_param (username , FTP_MAX_USERNAME );
572+ if (ret ) {
573+ return ret ;
574+ }
575+ ret = validate_ftp_param (password , FTP_MAX_PASSWORD );
576+ if (ret ) {
577+ return ret ;
578+ }
579+
527580 /* send username */
528- sprintf (ctrl_buf , CMD_USER , username );
581+ snprintf (ctrl_buf , sizeof ( ctrl_buf ) , CMD_USER , username );
529582 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
530583 if (ret ) {
531584 return ret ;
532585 }
533586 ret = do_ftp_recv_ctrl (true, FTP_CODE_331 );
534587 if (ret == FTP_CODE_331 ) {
535588 /* send password if requested */
536- sprintf (ctrl_buf , CMD_PASS , password );
589+ snprintf (ctrl_buf , sizeof ( ctrl_buf ) , CMD_PASS , password );
537590 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
538591 if (ret ) {
539592 return ret ;
@@ -630,7 +683,16 @@ int ftp_pwd(void)
630683int ftp_list (const char * options , const char * target )
631684{
632685 int ret ;
633- char list_cmd [128 ];
686+
687+ /* Validate inputs */
688+ ret = validate_ftp_param (options , FTP_MAX_OPTIONS );
689+ if (ret ) {
690+ return ret ;
691+ }
692+ ret = validate_ftp_param (target , FTP_MAX_PATHNAME );
693+ if (ret ) {
694+ return ret ;
695+ }
634696
635697 /* Always set Passive mode to act as TCP client */
636698 ret = do_ftp_send_ctrl (CMD_PASV , sizeof (CMD_PASV ) - 1 );
@@ -641,24 +703,25 @@ int ftp_list(const char *options, const char *target)
641703 if (ret != FTP_CODE_227 ) {
642704 return ret ;
643705 }
644- data_task_param .ctrl_msg = ctrl_buf ;
706+ strncpy (data_task_param .ctrl_msg , ctrl_buf , sizeof (data_task_param .ctrl_msg ) - 1 );
707+ data_task_param .ctrl_msg [sizeof (data_task_param .ctrl_msg ) - 1 ] = '\0' ;
645708 data_task_param .task = TASK_RECEIVE ;
646709
647710 /* Send LIST/NLST command in control channel */
648711 if (strlen (options ) != 0 ) {
649712 if (strlen (target ) != 0 ) {
650- sprintf ( list_cmd , CMD_LIST_OPT_FILE , options , target );
713+ snprintf ( ctrl_buf , sizeof ( ctrl_buf ) , CMD_LIST_OPT_FILE , options , target );
651714 } else {
652- sprintf ( list_cmd , CMD_LIST_OPT , options );
715+ snprintf ( ctrl_buf , sizeof ( ctrl_buf ) , CMD_LIST_OPT , options );
653716 }
654717 } else {
655718 if (strlen (target ) != 0 ) {
656- sprintf ( list_cmd , CMD_LIST_FILE , target );
719+ snprintf ( ctrl_buf , sizeof ( ctrl_buf ) , CMD_LIST_FILE , target );
657720 } else {
658- strcpy ( list_cmd , CMD_NLST );
721+ snprintf ( ctrl_buf , sizeof ( ctrl_buf ) , CMD_NLST );
659722 }
660723 }
661- ret = do_ftp_send_ctrl (list_cmd , strlen (list_cmd ));
724+ ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
662725
663726 return run_data_task ();
664727}
@@ -667,10 +730,16 @@ int ftp_cwd(const char *folder)
667730{
668731 int ret ;
669732
733+ /* Validate input */
734+ ret = validate_ftp_param (folder , FTP_MAX_PATHNAME );
735+ if (ret ) {
736+ return ret ;
737+ }
738+
670739 if (strcmp (folder , ".." ) == 0 ) {
671740 ret = do_ftp_send_ctrl (CMD_CDUP , sizeof (CMD_CDUP ) - 1 );
672741 } else {
673- sprintf (ctrl_buf , CMD_CWD , folder );
742+ snprintf (ctrl_buf , sizeof ( ctrl_buf ) , CMD_CWD , folder );
674743 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
675744 }
676745 if (ret == 0 ) {
@@ -684,7 +753,13 @@ int ftp_mkd(const char *folder)
684753{
685754 int ret ;
686755
687- sprintf (ctrl_buf , CMD_MKD , folder );
756+ /* Validate input */
757+ ret = validate_ftp_param (folder , FTP_MAX_PATHNAME );
758+ if (ret ) {
759+ return ret ;
760+ }
761+
762+ snprintf (ctrl_buf , sizeof (ctrl_buf ), CMD_MKD , folder );
688763 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
689764 if (ret == 0 ) {
690765 ret = do_ftp_recv_ctrl (true, FTP_CODE_257 );
@@ -697,7 +772,13 @@ int ftp_rmd(const char *folder)
697772{
698773 int ret ;
699774
700- sprintf (ctrl_buf , CMD_RMD , folder );
775+ /* Validate input */
776+ ret = validate_ftp_param (folder , FTP_MAX_PATHNAME );
777+ if (ret ) {
778+ return ret ;
779+ }
780+
781+ snprintf (ctrl_buf , sizeof (ctrl_buf ), CMD_RMD , folder );
701782 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
702783 if (ret == 0 ) {
703784 ret = do_ftp_recv_ctrl (true, FTP_CODE_250 );
@@ -710,7 +791,17 @@ int ftp_rename(const char *old_name, const char *new_name)
710791{
711792 int ret ;
712793
713- sprintf (ctrl_buf , CMD_RNFR , old_name );
794+ /* Validate inputs */
795+ ret = validate_ftp_param (old_name , FTP_MAX_PATHNAME );
796+ if (ret ) {
797+ return ret ;
798+ }
799+ ret = validate_ftp_param (new_name , FTP_MAX_PATHNAME );
800+ if (ret ) {
801+ return ret ;
802+ }
803+
804+ snprintf (ctrl_buf , sizeof (ctrl_buf ), CMD_RNFR , old_name );
714805 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
715806 if (ret == 0 ) {
716807 ret = do_ftp_recv_ctrl (true, FTP_CODE_350 );
@@ -719,7 +810,7 @@ int ftp_rename(const char *old_name, const char *new_name)
719810 return ret ;
720811 }
721812
722- sprintf (ctrl_buf , CMD_RNTO , new_name );
813+ snprintf (ctrl_buf , sizeof ( ctrl_buf ) , CMD_RNTO , new_name );
723814 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
724815 if (ret == 0 ) {
725816 ret = do_ftp_recv_ctrl (true, FTP_CODE_250 );
@@ -732,7 +823,13 @@ int ftp_delete(const char *file)
732823{
733824 int ret ;
734825
735- sprintf (ctrl_buf , CMD_DELE , file );
826+ /* Validate input */
827+ ret = validate_ftp_param (file , FTP_MAX_PATHNAME );
828+ if (ret ) {
829+ return ret ;
830+ }
831+
832+ snprintf (ctrl_buf , sizeof (ctrl_buf ), CMD_DELE , file );
736833 ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
737834 if (ret == 0 ) {
738835 ret = do_ftp_recv_ctrl (true, FTP_CODE_250 );
@@ -744,7 +841,12 @@ int ftp_delete(const char *file)
744841int ftp_get (const char * file )
745842{
746843 int ret ;
747- char get_cmd [128 ];
844+
845+ /* Validate input */
846+ ret = validate_ftp_param (file , FTP_MAX_FILENAME );
847+ if (ret ) {
848+ return ret ;
849+ }
748850
749851 /* Always set Passive mode to act as TCP client */
750852 ret = do_ftp_send_ctrl (CMD_PASV , sizeof (CMD_PASV ) - 1 );
@@ -755,12 +857,13 @@ int ftp_get(const char *file)
755857 if (ret != FTP_CODE_227 ) {
756858 return ret ;
757859 }
758- data_task_param .ctrl_msg = ctrl_buf ;
860+ strncpy (data_task_param .ctrl_msg , ctrl_buf , sizeof (data_task_param .ctrl_msg ) - 1 );
861+ data_task_param .ctrl_msg [sizeof (data_task_param .ctrl_msg ) - 1 ] = '\0' ;
759862 data_task_param .task = TASK_RECEIVE ;
760863
761864 /* Send RETR command in control channel */
762- sprintf ( get_cmd , CMD_RETR , file );
763- ret = do_ftp_send_ctrl (get_cmd , strlen (get_cmd ));
865+ snprintf ( ctrl_buf , sizeof ( ctrl_buf ) , CMD_RETR , file );
866+ ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
764867 if (ret ) {
765868 return - EIO ;
766869 }
@@ -771,7 +874,6 @@ int ftp_get(const char *file)
771874int ftp_put (const char * file , const uint8_t * data , uint16_t length , int type )
772875{
773876 int ret ;
774- char put_cmd [128 ];
775877
776878 if (type != FTP_PUT_NORMAL && type != FTP_PUT_UNIQUE && type != FTP_PUT_APPEND ) {
777879 return - EINVAL ;
@@ -781,6 +883,14 @@ int ftp_put(const char *file, const uint8_t *data, uint16_t length, int type)
781883 return - EINVAL ;
782884 }
783885
886+ /* Validate file parameter if provided */
887+ if (file != NULL ) {
888+ ret = validate_ftp_param (file , FTP_MAX_FILENAME );
889+ if (ret ) {
890+ return ret ;
891+ }
892+ }
893+
784894 /** Typical sequence:
785895 * FTP 51 Request: PASV
786896 * FTP 96 Response: 227 Entering Passive Mode (90,130,70,73,105,177).
@@ -800,24 +910,25 @@ int ftp_put(const char *file, const uint8_t *data, uint16_t length, int type)
800910 if (ret != FTP_CODE_227 ) {
801911 return ret ;
802912 }
803- data_task_param .ctrl_msg = ctrl_buf ;
913+ strncpy (data_task_param .ctrl_msg , ctrl_buf , sizeof (data_task_param .ctrl_msg ) - 1 );
914+ data_task_param .ctrl_msg [sizeof (data_task_param .ctrl_msg ) - 1 ] = '\0' ;
804915 data_task_param .task = TASK_SEND ;
805916 data_task_param .data = (uint8_t * )data ;
806917 data_task_param .length = length ;
807918 }
808919
809920 if (type == FTP_PUT_NORMAL ) {
810921 /* Send STOR command in control channel */
811- sprintf ( put_cmd , CMD_STOR , file );
812- ret = do_ftp_send_ctrl (put_cmd , strlen (put_cmd ));
922+ snprintf ( ctrl_buf , sizeof ( ctrl_buf ) , CMD_STOR , file );
923+ ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
813924 } else if (type == FTP_PUT_UNIQUE ) {
814925 /* Send STOU command in control channel */
815- sprintf ( put_cmd , CMD_STOU );
816- ret = do_ftp_send_ctrl (put_cmd , strlen (put_cmd ));
926+ snprintf ( ctrl_buf , sizeof ( ctrl_buf ) , CMD_STOU );
927+ ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
817928 } else {
818929 /* Send APPE command in control channel */
819- sprintf ( put_cmd , CMD_APPE , file );
820- ret = do_ftp_send_ctrl (put_cmd , strlen (put_cmd ));
930+ snprintf ( ctrl_buf , sizeof ( ctrl_buf ) , CMD_APPE , file );
931+ ret = do_ftp_send_ctrl (ctrl_buf , strlen (ctrl_buf ));
821932 }
822933 if (ret != 0 ) {
823934 return ret ;
0 commit comments